agentsecure 0.1.7__tar.gz → 0.1.9__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.
- {agentsecure-0.1.7/agentsecure.egg-info → agentsecure-0.1.9}/PKG-INFO +35 -1
- {agentsecure-0.1.7 → agentsecure-0.1.9}/README.md +34 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/__init__.py +1 -1
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/cli/main.py +300 -1
- agentsecure-0.1.9/agentsecure/core/agent_guidance.py +69 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9/agentsecure.egg-info}/PKG-INFO +35 -1
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure.egg-info/SOURCES.txt +2 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/setup.cfg +1 -1
- agentsecure-0.1.9/tests/test_agent_guidance.py +54 -0
- agentsecure-0.1.9/tests/test_secret_aliases.py +232 -0
- agentsecure-0.1.7/tests/test_secret_aliases.py +0 -95
- {agentsecure-0.1.7 → agentsecure-0.1.9}/LICENSE +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/NOTICE +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/__main__.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/api/__init__.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/api/server.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/api/services.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/cli/__init__.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/cli/common.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/cli/demo.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/cli/policy.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/cli/project.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/cli/proxy.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/cli/receipts.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/cli/secrets.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/cli/settings.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/client/__init__.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/client/wrappers.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/cloud.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/config/__init__.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/config/default_agentsecure.json +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/core/__init__.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/core/agentsecure_md.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/core/capabilities.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/core/command_metadata.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/core/config.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/core/config_profiles.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/core/container.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/core/key_service.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/core/models.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/core/policy_mutation.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/core/policy_ports.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/core/policy_response.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/core/policy_validation.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/core/product.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/core/provider_proxy.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/core/runtime_bindings.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/core/secret_aliases.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/core/time.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/crypto/__init__.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/crypto/cipher.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/crypto/key_provider.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/daemon/__init__.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/daemon/commands.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/daemon/policies.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/daemon/sessions.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/daemon/supervisor.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/discovery/__init__.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/discovery/dotenv_scanner.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/discovery/env_scanner.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/discovery/patterns.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/discovery/scanner.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/discovery/suggestions.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/gateway/__init__.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/gateway/proxy.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/guard/__init__.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/guard/command.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/guard/network.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/guard/sanitizer.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/guard/wrappers.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/implementations/__init__.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/implementations/audit.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/implementations/encrypted_secret_store.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/implementations/grant_store.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/implementations/local_secret_store.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/implementations/policy.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/implementations/secret_store_factory.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/implementations/secrets.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/interfaces/__init__.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/interfaces/audit.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/interfaces/grants.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/interfaces/key_store.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/interfaces/policy.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/interfaces/secrets.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/workspace/__init__.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/workspace/apply.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/workspace/diff.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/workspace/materializer.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/workspace/rewriter.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure/workspace/strategies.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure.egg-info/dependency_links.txt +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure.egg-info/entry_points.txt +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/agentsecure.egg-info/top_level.txt +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/pyproject.toml +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/setup.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/tests/test_agentsecure_md.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/tests/test_audit.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/tests/test_cli.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/tests/test_cli_demo.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/tests/test_cli_policy.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/tests/test_cli_proxy.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/tests/test_config_validation.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/tests/test_diff.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/tests/test_discovery.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/tests/test_encrypted_secret_store.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/tests/test_env_policy.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/tests/test_env_policy_contract.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/tests/test_gateway_credentials.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/tests/test_guard.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/tests/test_guard_network.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/tests/test_key_service.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/tests/test_policy.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/tests/test_policy_mutation.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/tests/test_product.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/tests/test_provider_proxy.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/tests/test_secret_scan.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/tests/test_secrets.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/tests/test_version.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/tests/test_workspace.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/tests/test_workspace_apply.py +0 -0
- {agentsecure-0.1.7 → agentsecure-0.1.9}/tests/test_wrappers.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentsecure
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.9
|
|
4
4
|
Summary: Local-first security runtime for AI coding agents
|
|
5
5
|
Home-page: https://github.com/ShellFrameAI/agentsecure-community
|
|
6
6
|
Author: ShellFrame AI
|
|
@@ -76,6 +76,23 @@ python3 -m agentsecure run claude
|
|
|
76
76
|
|
|
77
77
|
Keep real secrets in one local AgentSecure vault:
|
|
78
78
|
|
|
79
|
+
```bash
|
|
80
|
+
agentsecure secrets import .env
|
|
81
|
+
agentsecure run -- claude
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`secrets import` is the easiest migration path. It scans the dotenv file, stores discovered real secret values in the local vault, assigns those aliases to the current project, writes a private backup under `~/.agentsecure/backups/`, and replaces the values in `.env` with non-secret `AGENTSECURE_ALIAS_...` placeholders.
|
|
85
|
+
|
|
86
|
+
Use `--dry-run` to preview the import, or `--keep-file` if you want to store aliases without rewriting `.env`.
|
|
87
|
+
|
|
88
|
+
To undo the rewrite and bring the original `.env` back from the latest private backup:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
agentsecure secrets restore .env
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
For manual control, add one alias at a time:
|
|
95
|
+
|
|
79
96
|
```bash
|
|
80
97
|
printf '%s' "$DATABASE_URL" | agentsecure secrets add dev_db \
|
|
81
98
|
--env-name DATABASE_URL \
|
|
@@ -97,6 +114,23 @@ What this does:
|
|
|
97
114
|
|
|
98
115
|
Do not put real secrets in project `.env` files. Use `.env` for non-secret config or fake placeholders that are safe for an agent to read.
|
|
99
116
|
|
|
117
|
+
## Agent Run Guidance
|
|
118
|
+
|
|
119
|
+
Every `agentsecure run` creates a small per-run guide under `.agentsecure/runs/` and prints its relative path:
|
|
120
|
+
|
|
121
|
+
```text
|
|
122
|
+
AgentSecure agent guide: .agentsecure/runs/run_.../AGENTSECURE_AGENT_GUIDE.md
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The launched agent receives the absolute guide path in both `AGENTSECURE_AGENT_GUIDE` and `AGENTSECURE_SKILL_FILE`. The file contains only operational guidance and safe metadata, such as managed secret environment variable names, providers, and approved hosts from runtime alias bindings when available. It does not include raw secrets or virtual token values.
|
|
126
|
+
|
|
127
|
+
Agents should use the injected environment variables and virtual tokens. They should not read `.env` to recover secrets or ask a human to paste secrets. If an expected secret env var is missing, ask the user to run:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
agentsecure secrets import .env
|
|
131
|
+
agentsecure secrets use <alias>
|
|
132
|
+
```
|
|
133
|
+
|
|
100
134
|
## What The Demo Shows
|
|
101
135
|
|
|
102
136
|
The built-in demo creates a temporary local project with fake secrets, applies a small sample policy, simulates a command reading `.env`, and prints what the agent would see:
|
|
@@ -50,6 +50,23 @@ python3 -m agentsecure run claude
|
|
|
50
50
|
|
|
51
51
|
Keep real secrets in one local AgentSecure vault:
|
|
52
52
|
|
|
53
|
+
```bash
|
|
54
|
+
agentsecure secrets import .env
|
|
55
|
+
agentsecure run -- claude
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`secrets import` is the easiest migration path. It scans the dotenv file, stores discovered real secret values in the local vault, assigns those aliases to the current project, writes a private backup under `~/.agentsecure/backups/`, and replaces the values in `.env` with non-secret `AGENTSECURE_ALIAS_...` placeholders.
|
|
59
|
+
|
|
60
|
+
Use `--dry-run` to preview the import, or `--keep-file` if you want to store aliases without rewriting `.env`.
|
|
61
|
+
|
|
62
|
+
To undo the rewrite and bring the original `.env` back from the latest private backup:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
agentsecure secrets restore .env
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
For manual control, add one alias at a time:
|
|
69
|
+
|
|
53
70
|
```bash
|
|
54
71
|
printf '%s' "$DATABASE_URL" | agentsecure secrets add dev_db \
|
|
55
72
|
--env-name DATABASE_URL \
|
|
@@ -71,6 +88,23 @@ What this does:
|
|
|
71
88
|
|
|
72
89
|
Do not put real secrets in project `.env` files. Use `.env` for non-secret config or fake placeholders that are safe for an agent to read.
|
|
73
90
|
|
|
91
|
+
## Agent Run Guidance
|
|
92
|
+
|
|
93
|
+
Every `agentsecure run` creates a small per-run guide under `.agentsecure/runs/` and prints its relative path:
|
|
94
|
+
|
|
95
|
+
```text
|
|
96
|
+
AgentSecure agent guide: .agentsecure/runs/run_.../AGENTSECURE_AGENT_GUIDE.md
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The launched agent receives the absolute guide path in both `AGENTSECURE_AGENT_GUIDE` and `AGENTSECURE_SKILL_FILE`. The file contains only operational guidance and safe metadata, such as managed secret environment variable names, providers, and approved hosts from runtime alias bindings when available. It does not include raw secrets or virtual token values.
|
|
100
|
+
|
|
101
|
+
Agents should use the injected environment variables and virtual tokens. They should not read `.env` to recover secrets or ask a human to paste secrets. If an expected secret env var is missing, ask the user to run:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
agentsecure secrets import .env
|
|
105
|
+
agentsecure secrets use <alias>
|
|
106
|
+
```
|
|
107
|
+
|
|
74
108
|
## What The Demo Shows
|
|
75
109
|
|
|
76
110
|
The built-in demo creates a temporary local project with fake secrets, applies a small sample policy, simulates a command reading `.env`, and prints what the agent would see:
|
|
@@ -5,6 +5,7 @@ import ipaddress
|
|
|
5
5
|
import json
|
|
6
6
|
import os
|
|
7
7
|
import queue
|
|
8
|
+
import re
|
|
8
9
|
import shutil
|
|
9
10
|
import socket
|
|
10
11
|
import subprocess
|
|
@@ -14,6 +15,7 @@ import threading
|
|
|
14
15
|
import time
|
|
15
16
|
import uuid
|
|
16
17
|
from typing import Any, Dict, List, Optional
|
|
18
|
+
from urllib.parse import urlsplit
|
|
17
19
|
from urllib.request import Request, urlopen
|
|
18
20
|
|
|
19
21
|
from agentsecure import __version__
|
|
@@ -54,6 +56,12 @@ from agentsecure.cli.settings import (
|
|
|
54
56
|
)
|
|
55
57
|
from agentsecure.client.wrappers import AgentWrapperInstaller, SUPPORTED_AGENTS
|
|
56
58
|
from agentsecure.core.agentsecure_md import AGENTSECURE_MD, agentsecure_md_status
|
|
59
|
+
from agentsecure.core.agent_guidance import (
|
|
60
|
+
ENV_AGENT_GUIDE,
|
|
61
|
+
ENV_SKILL_FILE,
|
|
62
|
+
relative_agent_guidance_path,
|
|
63
|
+
write_agent_guidance,
|
|
64
|
+
)
|
|
57
65
|
from agentsecure.core.command_metadata import safe_command_metadata
|
|
58
66
|
from agentsecure.core.config import ConfigError, JsonConfigLoader, JsonConfigWriter
|
|
59
67
|
from agentsecure.core.config_profiles import (
|
|
@@ -256,6 +264,17 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
256
264
|
use_secret_parser = secrets_subparsers.add_parser("use", help="Assign aliases to this project")
|
|
257
265
|
use_secret_parser.add_argument("alias_ids", nargs="+")
|
|
258
266
|
use_secret_parser.add_argument("--project", default="", help="Project name for audit metadata")
|
|
267
|
+
import_secret_parser = secrets_subparsers.add_parser("import", help="Move secrets from a dotenv file into the local vault")
|
|
268
|
+
import_secret_parser.add_argument("path", nargs="?", default=".env", help="Dotenv file to import, default .env")
|
|
269
|
+
import_secret_parser.add_argument("--project", default="", help="Project name for audit metadata")
|
|
270
|
+
import_secret_parser.add_argument("--approved-host", action="append", default=[], help="Additional host allowed to receive imported secrets")
|
|
271
|
+
import_secret_parser.add_argument("--keep-file", action="store_true", help="Do not rewrite the dotenv file after importing")
|
|
272
|
+
import_secret_parser.add_argument("--no-backup", action="store_true", help="Do not store a local backup before rewriting")
|
|
273
|
+
import_secret_parser.add_argument("--dry-run", action="store_true", help="Show what would be imported without writing changes")
|
|
274
|
+
restore_secret_parser = secrets_subparsers.add_parser("restore", help="Restore a dotenv file from the latest AgentSecure backup")
|
|
275
|
+
restore_secret_parser.add_argument("path", nargs="?", default=".env", help="Dotenv file to restore, default .env")
|
|
276
|
+
restore_secret_parser.add_argument("--backup", default="", help="Specific backup file to restore instead of the latest backup")
|
|
277
|
+
restore_secret_parser.add_argument("--dry-run", action="store_true", help="Show which backup would be restored")
|
|
259
278
|
|
|
260
279
|
subparsers.add_parser("discover", help="Discover likely local secrets")
|
|
261
280
|
subparsers.add_parser("suggest", help="Suggest env and network policy for discovered secrets")
|
|
@@ -367,7 +386,8 @@ def run_agent(args: argparse.Namespace) -> int:
|
|
|
367
386
|
runtime_bindings = alias_runtime_bindings
|
|
368
387
|
container = Container.from_config_path(args.config, runtime_bindings=runtime_bindings, run_id=run_id)
|
|
369
388
|
replacements.extend(_configured_secret_replacements(container, replacements))
|
|
370
|
-
|
|
389
|
+
source_root = os.getcwd()
|
|
390
|
+
run_cwd = source_root
|
|
371
391
|
workspace_session = None
|
|
372
392
|
materializer = WorkspaceMaterializer(_workspace_base_for_runtime(run_cwd, args.runtime))
|
|
373
393
|
should_create_workspace = bool(replacements or container.config.files.protect_write)
|
|
@@ -453,6 +473,24 @@ def run_agent(args: argparse.Namespace) -> int:
|
|
|
453
473
|
env[ENV_RUNTIME_BINDINGS] = serialize_runtime_bindings(runtime_bindings)
|
|
454
474
|
env["AGENTSECURE_RUN_ID"] = run_id
|
|
455
475
|
env["AGENTSECURE_PROJECT_ID"] = project_id
|
|
476
|
+
try:
|
|
477
|
+
agent_guide_path = write_agent_guidance(source_root, run_id, runtime_bindings)
|
|
478
|
+
except OSError as exc:
|
|
479
|
+
sys.stderr.write("agentsecure: failed to create agent guidance file: %s\n" % exc)
|
|
480
|
+
return 1
|
|
481
|
+
env[ENV_SKILL_FILE] = agent_guide_path
|
|
482
|
+
env[ENV_AGENT_GUIDE] = agent_guide_path
|
|
483
|
+
relative_guide_path = relative_agent_guidance_path(source_root, agent_guide_path)
|
|
484
|
+
container.audit_logger.record(
|
|
485
|
+
"agent_guidance_created",
|
|
486
|
+
{
|
|
487
|
+
"run_id": run_id,
|
|
488
|
+
"project_id": project_id,
|
|
489
|
+
"path": relative_guide_path,
|
|
490
|
+
"aliases": [binding.alias_id or binding.env_name for binding in runtime_bindings],
|
|
491
|
+
},
|
|
492
|
+
)
|
|
493
|
+
print("AgentSecure agent guide: %s" % relative_guide_path, flush=True)
|
|
456
494
|
if args.runtime == "command-guard":
|
|
457
495
|
CommandGuardWrapperInstaller(args.config).install(env)
|
|
458
496
|
session_id = daemon_session.get("session_id", "") if daemon_session else ""
|
|
@@ -1149,6 +1187,10 @@ def handle_secret_aliases(args: argparse.Namespace) -> int:
|
|
|
1149
1187
|
return list_secret_aliases(args)
|
|
1150
1188
|
if args.secrets_command == "use":
|
|
1151
1189
|
return use_secret_aliases(args)
|
|
1190
|
+
if args.secrets_command == "import":
|
|
1191
|
+
return import_secret_aliases(args)
|
|
1192
|
+
if args.secrets_command == "restore":
|
|
1193
|
+
return restore_dotenv_from_backup(args)
|
|
1152
1194
|
sys.stderr.write("agentsecure: missing secrets subcommand\n")
|
|
1153
1195
|
return 2
|
|
1154
1196
|
|
|
@@ -1242,6 +1284,263 @@ def use_secret_aliases(args: argparse.Namespace) -> int:
|
|
|
1242
1284
|
return 0
|
|
1243
1285
|
|
|
1244
1286
|
|
|
1287
|
+
def import_secret_aliases(args: argparse.Namespace) -> int:
|
|
1288
|
+
dotenv_path = os.path.abspath(args.path)
|
|
1289
|
+
if not os.path.exists(dotenv_path):
|
|
1290
|
+
sys.stderr.write("agentsecure: dotenv file not found: %s\n" % args.path)
|
|
1291
|
+
return 2
|
|
1292
|
+
source_dir = os.path.dirname(dotenv_path) or "."
|
|
1293
|
+
source_name = os.path.basename(dotenv_path)
|
|
1294
|
+
discoveries = DotenvSecretScanner(source_dir, [source_name]).scan()
|
|
1295
|
+
discoveries = [secret for secret in discoveries if secret.source == source_name]
|
|
1296
|
+
if not discoveries:
|
|
1297
|
+
print(
|
|
1298
|
+
json.dumps(
|
|
1299
|
+
{
|
|
1300
|
+
"imported": [],
|
|
1301
|
+
"dotenv": args.path,
|
|
1302
|
+
"message": "No secrets found",
|
|
1303
|
+
},
|
|
1304
|
+
indent=2,
|
|
1305
|
+
sort_keys=True,
|
|
1306
|
+
)
|
|
1307
|
+
)
|
|
1308
|
+
return 0
|
|
1309
|
+
|
|
1310
|
+
imports = []
|
|
1311
|
+
for secret in discoveries:
|
|
1312
|
+
alias_id = _alias_id_for_env_name(secret.name)
|
|
1313
|
+
approved_hosts = _approved_hosts_for_import(secret, args.approved_host)
|
|
1314
|
+
imports.append(
|
|
1315
|
+
{
|
|
1316
|
+
"alias_id": alias_id,
|
|
1317
|
+
"env_name": secret.name,
|
|
1318
|
+
"provider": secret.provider_hint,
|
|
1319
|
+
"approved_hosts": approved_hosts,
|
|
1320
|
+
"placeholder": _alias_placeholder(alias_id),
|
|
1321
|
+
}
|
|
1322
|
+
)
|
|
1323
|
+
|
|
1324
|
+
if args.dry_run:
|
|
1325
|
+
print(
|
|
1326
|
+
json.dumps(
|
|
1327
|
+
{
|
|
1328
|
+
"dotenv": args.path,
|
|
1329
|
+
"imported": imports,
|
|
1330
|
+
"would_rewrite_dotenv": not args.keep_file,
|
|
1331
|
+
"dry_run": True,
|
|
1332
|
+
},
|
|
1333
|
+
indent=2,
|
|
1334
|
+
sort_keys=True,
|
|
1335
|
+
)
|
|
1336
|
+
)
|
|
1337
|
+
return 0
|
|
1338
|
+
|
|
1339
|
+
service = _secret_alias_service(args.config)
|
|
1340
|
+
try:
|
|
1341
|
+
for secret, item in zip(discoveries, imports):
|
|
1342
|
+
service.add_alias(
|
|
1343
|
+
alias_id=item["alias_id"],
|
|
1344
|
+
real_secret=secret.value,
|
|
1345
|
+
env_name=secret.name,
|
|
1346
|
+
provider=secret.provider_hint,
|
|
1347
|
+
approved_hosts=item["approved_hosts"],
|
|
1348
|
+
name=secret.name,
|
|
1349
|
+
)
|
|
1350
|
+
assigned = service.assign_to_project(
|
|
1351
|
+
args.config,
|
|
1352
|
+
[item["alias_id"] for item in imports],
|
|
1353
|
+
project=args.project or os.path.basename(os.getcwd()) or "default",
|
|
1354
|
+
)
|
|
1355
|
+
except SecretAliasError as exc:
|
|
1356
|
+
sys.stderr.write("agentsecure: %s\n" % exc)
|
|
1357
|
+
return 2
|
|
1358
|
+
|
|
1359
|
+
backup_path = ""
|
|
1360
|
+
if not args.keep_file:
|
|
1361
|
+
if not args.no_backup:
|
|
1362
|
+
try:
|
|
1363
|
+
backup_path = _backup_dotenv_to_vault(dotenv_path, args.config)
|
|
1364
|
+
except OSError as exc:
|
|
1365
|
+
sys.stderr.write("agentsecure: failed to back up dotenv file: %s\n" % exc)
|
|
1366
|
+
return 1
|
|
1367
|
+
try:
|
|
1368
|
+
_rewrite_dotenv_with_alias_placeholders(dotenv_path, {item["env_name"]: item["placeholder"] for item in imports})
|
|
1369
|
+
except OSError as exc:
|
|
1370
|
+
sys.stderr.write("agentsecure: failed to rewrite dotenv file: %s\n" % exc)
|
|
1371
|
+
return 1
|
|
1372
|
+
|
|
1373
|
+
print(
|
|
1374
|
+
json.dumps(
|
|
1375
|
+
{
|
|
1376
|
+
"dotenv": args.path,
|
|
1377
|
+
"backup": backup_path,
|
|
1378
|
+
"rewritten": not args.keep_file,
|
|
1379
|
+
"imported": [
|
|
1380
|
+
{
|
|
1381
|
+
"alias_id": item.alias_id,
|
|
1382
|
+
"env_name": item.env_name,
|
|
1383
|
+
"provider": item.provider,
|
|
1384
|
+
"approved_hosts": item.approved_hosts,
|
|
1385
|
+
}
|
|
1386
|
+
for item in assigned
|
|
1387
|
+
],
|
|
1388
|
+
"real_secrets_stored": "local_vault",
|
|
1389
|
+
},
|
|
1390
|
+
indent=2,
|
|
1391
|
+
sort_keys=True,
|
|
1392
|
+
)
|
|
1393
|
+
)
|
|
1394
|
+
return 0
|
|
1395
|
+
|
|
1396
|
+
|
|
1397
|
+
def restore_dotenv_from_backup(args: argparse.Namespace) -> int:
|
|
1398
|
+
dotenv_path = os.path.abspath(args.path)
|
|
1399
|
+
backup_path = os.path.abspath(args.backup) if args.backup else _latest_dotenv_backup(dotenv_path, args.config)
|
|
1400
|
+
if not backup_path:
|
|
1401
|
+
sys.stderr.write("agentsecure: no backup found for %s\n" % args.path)
|
|
1402
|
+
return 2
|
|
1403
|
+
if not os.path.exists(backup_path):
|
|
1404
|
+
sys.stderr.write("agentsecure: backup file not found: %s\n" % backup_path)
|
|
1405
|
+
return 2
|
|
1406
|
+
if args.dry_run:
|
|
1407
|
+
print(
|
|
1408
|
+
json.dumps(
|
|
1409
|
+
{
|
|
1410
|
+
"dotenv": args.path,
|
|
1411
|
+
"backup": backup_path,
|
|
1412
|
+
"would_restore": True,
|
|
1413
|
+
"dry_run": True,
|
|
1414
|
+
},
|
|
1415
|
+
indent=2,
|
|
1416
|
+
sort_keys=True,
|
|
1417
|
+
)
|
|
1418
|
+
)
|
|
1419
|
+
return 0
|
|
1420
|
+
try:
|
|
1421
|
+
shutil.copy2(backup_path, dotenv_path)
|
|
1422
|
+
except OSError as exc:
|
|
1423
|
+
sys.stderr.write("agentsecure: failed to restore dotenv file: %s\n" % exc)
|
|
1424
|
+
return 1
|
|
1425
|
+
print(
|
|
1426
|
+
json.dumps(
|
|
1427
|
+
{
|
|
1428
|
+
"dotenv": args.path,
|
|
1429
|
+
"backup": backup_path,
|
|
1430
|
+
"restored": True,
|
|
1431
|
+
},
|
|
1432
|
+
indent=2,
|
|
1433
|
+
sort_keys=True,
|
|
1434
|
+
)
|
|
1435
|
+
)
|
|
1436
|
+
return 0
|
|
1437
|
+
|
|
1438
|
+
|
|
1439
|
+
def _alias_id_for_env_name(env_name: str) -> str:
|
|
1440
|
+
normalized = re.sub(r"[^a-z0-9._-]+", "_", str(env_name).strip().lower())
|
|
1441
|
+
normalized = normalized.strip("._-")
|
|
1442
|
+
return normalized or "secret"
|
|
1443
|
+
|
|
1444
|
+
|
|
1445
|
+
def _alias_placeholder(alias_id: str) -> str:
|
|
1446
|
+
return "AGENTSECURE_ALIAS_%s" % re.sub(r"[^A-Z0-9]+", "_", alias_id.upper()).strip("_")
|
|
1447
|
+
|
|
1448
|
+
|
|
1449
|
+
def _approved_hosts_for_import(secret: DiscoveredSecret, extra_hosts: List[str]) -> List[str]:
|
|
1450
|
+
hosts = []
|
|
1451
|
+
inferred = _host_from_secret_value(secret.value)
|
|
1452
|
+
if inferred:
|
|
1453
|
+
hosts.append(inferred)
|
|
1454
|
+
provider_hosts = {
|
|
1455
|
+
"openai": "api.openai.com",
|
|
1456
|
+
"anthropic": "api.anthropic.com",
|
|
1457
|
+
"github": "api.github.com",
|
|
1458
|
+
"stripe": "api.stripe.com",
|
|
1459
|
+
}
|
|
1460
|
+
provider_host = provider_hosts.get(secret.provider_hint)
|
|
1461
|
+
if provider_host:
|
|
1462
|
+
hosts.append(provider_host)
|
|
1463
|
+
hosts.extend(extra_hosts or [])
|
|
1464
|
+
result = []
|
|
1465
|
+
seen = set()
|
|
1466
|
+
for host in hosts:
|
|
1467
|
+
normalized = str(host).strip().lower().rstrip(".")
|
|
1468
|
+
if normalized and normalized not in seen:
|
|
1469
|
+
result.append(normalized)
|
|
1470
|
+
seen.add(normalized)
|
|
1471
|
+
return result
|
|
1472
|
+
|
|
1473
|
+
|
|
1474
|
+
def _host_from_secret_value(value: str) -> str:
|
|
1475
|
+
try:
|
|
1476
|
+
parsed = urlsplit(str(value).strip())
|
|
1477
|
+
except ValueError:
|
|
1478
|
+
return ""
|
|
1479
|
+
return (parsed.hostname or "").strip().lower().rstrip(".")
|
|
1480
|
+
|
|
1481
|
+
|
|
1482
|
+
def _backup_dotenv_to_vault(dotenv_path: str, config_path: str) -> str:
|
|
1483
|
+
backup_dir = os.path.join(agentsecure_home(), "backups", project_id_for_path(config_path))
|
|
1484
|
+
os.makedirs(backup_dir, exist_ok=True)
|
|
1485
|
+
backup_path = os.path.join(
|
|
1486
|
+
backup_dir,
|
|
1487
|
+
"%s.%s.bak" % (os.path.basename(dotenv_path), time.strftime("%Y%m%d%H%M%S")),
|
|
1488
|
+
)
|
|
1489
|
+
shutil.copy2(dotenv_path, backup_path)
|
|
1490
|
+
os.chmod(backup_path, 0o600)
|
|
1491
|
+
return backup_path
|
|
1492
|
+
|
|
1493
|
+
|
|
1494
|
+
def _latest_dotenv_backup(dotenv_path: str, config_path: str) -> str:
|
|
1495
|
+
backup_dir = os.path.join(agentsecure_home(), "backups", project_id_for_path(config_path))
|
|
1496
|
+
if not os.path.isdir(backup_dir):
|
|
1497
|
+
return ""
|
|
1498
|
+
prefix = os.path.basename(dotenv_path) + "."
|
|
1499
|
+
candidates = []
|
|
1500
|
+
for filename in os.listdir(backup_dir):
|
|
1501
|
+
if filename.startswith(prefix) and filename.endswith(".bak"):
|
|
1502
|
+
path = os.path.join(backup_dir, filename)
|
|
1503
|
+
if os.path.isfile(path):
|
|
1504
|
+
candidates.append(path)
|
|
1505
|
+
if not candidates:
|
|
1506
|
+
return ""
|
|
1507
|
+
candidates.sort(key=lambda path: (os.path.getmtime(path), path), reverse=True)
|
|
1508
|
+
return candidates[0]
|
|
1509
|
+
|
|
1510
|
+
|
|
1511
|
+
def _rewrite_dotenv_with_alias_placeholders(dotenv_path: str, placeholders: Dict[str, str]) -> None:
|
|
1512
|
+
with open(dotenv_path, "r") as handle:
|
|
1513
|
+
lines = handle.readlines()
|
|
1514
|
+
rewritten = []
|
|
1515
|
+
for line in lines:
|
|
1516
|
+
parsed_name = _dotenv_line_name(line)
|
|
1517
|
+
if parsed_name and parsed_name in placeholders:
|
|
1518
|
+
prefix = line.split("=", 1)[0].rstrip()
|
|
1519
|
+
newline = "\n" if line.endswith("\n") else ""
|
|
1520
|
+
rewritten.append("%s=%s%s" % (prefix, placeholders[parsed_name], newline))
|
|
1521
|
+
else:
|
|
1522
|
+
rewritten.append(line)
|
|
1523
|
+
fd, temp_path = tempfile.mkstemp(prefix=".agentsecure-dotenv-", dir=os.path.dirname(dotenv_path) or ".")
|
|
1524
|
+
try:
|
|
1525
|
+
with os.fdopen(fd, "w") as handle:
|
|
1526
|
+
handle.writelines(rewritten)
|
|
1527
|
+
shutil.copymode(dotenv_path, temp_path)
|
|
1528
|
+
os.replace(temp_path, dotenv_path)
|
|
1529
|
+
finally:
|
|
1530
|
+
if os.path.exists(temp_path):
|
|
1531
|
+
os.unlink(temp_path)
|
|
1532
|
+
|
|
1533
|
+
|
|
1534
|
+
def _dotenv_line_name(line: str) -> str:
|
|
1535
|
+
stripped = line.strip()
|
|
1536
|
+
if not stripped or stripped.startswith("#") or "=" not in stripped:
|
|
1537
|
+
return ""
|
|
1538
|
+
name = stripped.split("=", 1)[0].strip()
|
|
1539
|
+
if name.startswith("export "):
|
|
1540
|
+
name = name[len("export ") :].strip()
|
|
1541
|
+
return name
|
|
1542
|
+
|
|
1543
|
+
|
|
1245
1544
|
def _secret_alias_service(config_path: str) -> SecretAliasService:
|
|
1246
1545
|
home = agentsecure_home()
|
|
1247
1546
|
return SecretAliasService(
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Iterable, List
|
|
3
|
+
|
|
4
|
+
from agentsecure.core.models import SecretBinding
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
AGENT_GUIDE_FILENAME = "AGENTSECURE_AGENT_GUIDE.md"
|
|
8
|
+
ENV_AGENT_GUIDE = "AGENTSECURE_AGENT_GUIDE"
|
|
9
|
+
ENV_SKILL_FILE = "AGENTSECURE_SKILL_FILE"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def write_agent_guidance(source_root: str, run_id: str, bindings: Iterable[SecretBinding]) -> str:
|
|
13
|
+
guide_dir = os.path.abspath(os.path.join(source_root, ".agentsecure", "runs", run_id))
|
|
14
|
+
os.makedirs(guide_dir, exist_ok=True)
|
|
15
|
+
os.chmod(guide_dir, 0o700)
|
|
16
|
+
guide_path = os.path.join(guide_dir, AGENT_GUIDE_FILENAME)
|
|
17
|
+
fd = os.open(guide_path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
|
|
18
|
+
with os.fdopen(fd, "w") as handle:
|
|
19
|
+
handle.write(render_agent_guidance(bindings))
|
|
20
|
+
os.chmod(guide_path, 0o600)
|
|
21
|
+
return guide_path
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def render_agent_guidance(bindings: Iterable[SecretBinding]) -> str:
|
|
25
|
+
binding_list = sorted(list(bindings), key=lambda item: (item.env_name, item.provider, item.alias_id))
|
|
26
|
+
lines: List[str] = [
|
|
27
|
+
"# AgentSecure Run Guidance",
|
|
28
|
+
"",
|
|
29
|
+
"- Use the environment variables AgentSecure injected for this run.",
|
|
30
|
+
"- Treat values beginning with `virt_` as virtual tokens, not raw secrets.",
|
|
31
|
+
"- Do not read `.env` files to recover secrets.",
|
|
32
|
+
"- Do not ask a human to paste secrets into chat, terminal, logs, or files.",
|
|
33
|
+
"- If a required secret environment variable is missing, ask the user to run `agentsecure secrets import .env` or `agentsecure secrets use <alias>` before retrying.",
|
|
34
|
+
"",
|
|
35
|
+
]
|
|
36
|
+
if binding_list:
|
|
37
|
+
lines.extend(["## Managed Secret Environment", ""])
|
|
38
|
+
for binding in binding_list:
|
|
39
|
+
details = [
|
|
40
|
+
"env=%s" % _safe_text(binding.env_name),
|
|
41
|
+
"provider=%s" % _safe_text(binding.provider or "custom"),
|
|
42
|
+
]
|
|
43
|
+
if binding.approved_hosts:
|
|
44
|
+
details.append("approved_hosts=%s" % ", ".join(_safe_text(host) for host in binding.approved_hosts))
|
|
45
|
+
else:
|
|
46
|
+
details.append("approved_hosts=not specified")
|
|
47
|
+
lines.append("- " + "; ".join(details))
|
|
48
|
+
lines.append("")
|
|
49
|
+
else:
|
|
50
|
+
lines.extend(
|
|
51
|
+
[
|
|
52
|
+
"## Managed Secret Environment",
|
|
53
|
+
"",
|
|
54
|
+
"- No runtime alias bindings were provided for this run.",
|
|
55
|
+
"",
|
|
56
|
+
]
|
|
57
|
+
)
|
|
58
|
+
return "\n".join(lines)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def relative_agent_guidance_path(source_root: str, guide_path: str) -> str:
|
|
62
|
+
try:
|
|
63
|
+
return os.path.relpath(guide_path, os.path.abspath(source_root))
|
|
64
|
+
except ValueError:
|
|
65
|
+
return guide_path
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _safe_text(value: str) -> str:
|
|
69
|
+
return str(value).replace("\n", " ").replace("\r", " ").strip()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentsecure
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.9
|
|
4
4
|
Summary: Local-first security runtime for AI coding agents
|
|
5
5
|
Home-page: https://github.com/ShellFrameAI/agentsecure-community
|
|
6
6
|
Author: ShellFrame AI
|
|
@@ -76,6 +76,23 @@ python3 -m agentsecure run claude
|
|
|
76
76
|
|
|
77
77
|
Keep real secrets in one local AgentSecure vault:
|
|
78
78
|
|
|
79
|
+
```bash
|
|
80
|
+
agentsecure secrets import .env
|
|
81
|
+
agentsecure run -- claude
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`secrets import` is the easiest migration path. It scans the dotenv file, stores discovered real secret values in the local vault, assigns those aliases to the current project, writes a private backup under `~/.agentsecure/backups/`, and replaces the values in `.env` with non-secret `AGENTSECURE_ALIAS_...` placeholders.
|
|
85
|
+
|
|
86
|
+
Use `--dry-run` to preview the import, or `--keep-file` if you want to store aliases without rewriting `.env`.
|
|
87
|
+
|
|
88
|
+
To undo the rewrite and bring the original `.env` back from the latest private backup:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
agentsecure secrets restore .env
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
For manual control, add one alias at a time:
|
|
95
|
+
|
|
79
96
|
```bash
|
|
80
97
|
printf '%s' "$DATABASE_URL" | agentsecure secrets add dev_db \
|
|
81
98
|
--env-name DATABASE_URL \
|
|
@@ -97,6 +114,23 @@ What this does:
|
|
|
97
114
|
|
|
98
115
|
Do not put real secrets in project `.env` files. Use `.env` for non-secret config or fake placeholders that are safe for an agent to read.
|
|
99
116
|
|
|
117
|
+
## Agent Run Guidance
|
|
118
|
+
|
|
119
|
+
Every `agentsecure run` creates a small per-run guide under `.agentsecure/runs/` and prints its relative path:
|
|
120
|
+
|
|
121
|
+
```text
|
|
122
|
+
AgentSecure agent guide: .agentsecure/runs/run_.../AGENTSECURE_AGENT_GUIDE.md
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The launched agent receives the absolute guide path in both `AGENTSECURE_AGENT_GUIDE` and `AGENTSECURE_SKILL_FILE`. The file contains only operational guidance and safe metadata, such as managed secret environment variable names, providers, and approved hosts from runtime alias bindings when available. It does not include raw secrets or virtual token values.
|
|
126
|
+
|
|
127
|
+
Agents should use the injected environment variables and virtual tokens. They should not read `.env` to recover secrets or ask a human to paste secrets. If an expected secret env var is missing, ask the user to run:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
agentsecure secrets import .env
|
|
131
|
+
agentsecure secrets use <alias>
|
|
132
|
+
```
|
|
133
|
+
|
|
100
134
|
## What The Demo Shows
|
|
101
135
|
|
|
102
136
|
The built-in demo creates a temporary local project with fake secrets, applies a small sample policy, simulates a command reading `.env`, and prints what the agent would see:
|
|
@@ -30,6 +30,7 @@ agentsecure/client/wrappers.py
|
|
|
30
30
|
agentsecure/config/__init__.py
|
|
31
31
|
agentsecure/config/default_agentsecure.json
|
|
32
32
|
agentsecure/core/__init__.py
|
|
33
|
+
agentsecure/core/agent_guidance.py
|
|
33
34
|
agentsecure/core/agentsecure_md.py
|
|
34
35
|
agentsecure/core/capabilities.py
|
|
35
36
|
agentsecure/core/command_metadata.py
|
|
@@ -88,6 +89,7 @@ agentsecure/workspace/diff.py
|
|
|
88
89
|
agentsecure/workspace/materializer.py
|
|
89
90
|
agentsecure/workspace/rewriter.py
|
|
90
91
|
agentsecure/workspace/strategies.py
|
|
92
|
+
tests/test_agent_guidance.py
|
|
91
93
|
tests/test_agentsecure_md.py
|
|
92
94
|
tests/test_audit.py
|
|
93
95
|
tests/test_cli.py
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import tempfile
|
|
3
|
+
import unittest
|
|
4
|
+
|
|
5
|
+
from agentsecure.core.agent_guidance import (
|
|
6
|
+
AGENT_GUIDE_FILENAME,
|
|
7
|
+
render_agent_guidance,
|
|
8
|
+
relative_agent_guidance_path,
|
|
9
|
+
write_agent_guidance,
|
|
10
|
+
)
|
|
11
|
+
from agentsecure.core.models import SecretBinding
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AgentGuidanceTest(unittest.TestCase):
|
|
15
|
+
def test_renders_managed_bindings_without_virtual_or_real_secret_values(self):
|
|
16
|
+
text = render_agent_guidance(
|
|
17
|
+
[
|
|
18
|
+
SecretBinding(
|
|
19
|
+
env_name="OPENAI_API_KEY",
|
|
20
|
+
virtual_token="virt_openai_should_not_be_written",
|
|
21
|
+
real_secret_ref="vault://secret-ref-should-not-be-written",
|
|
22
|
+
provider="openai",
|
|
23
|
+
alias_id="openai_dev",
|
|
24
|
+
approved_hosts=["api.openai.com"],
|
|
25
|
+
)
|
|
26
|
+
]
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
self.assertIn("OPENAI_API_KEY", text)
|
|
30
|
+
self.assertIn("provider=openai", text)
|
|
31
|
+
self.assertIn("approved_hosts=api.openai.com", text)
|
|
32
|
+
self.assertIn("Do not read `.env` files", text)
|
|
33
|
+
self.assertIn("agentsecure secrets import .env", text)
|
|
34
|
+
self.assertIn("agentsecure secrets use <alias>", text)
|
|
35
|
+
self.assertNotIn("virt_openai_should_not_be_written", text)
|
|
36
|
+
self.assertNotIn("vault://secret-ref-should-not-be-written", text)
|
|
37
|
+
|
|
38
|
+
def test_writes_per_run_guidance_under_agentsecure_state(self):
|
|
39
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
40
|
+
path = write_agent_guidance(temp_dir, "run_test", [])
|
|
41
|
+
|
|
42
|
+
self.assertEqual(
|
|
43
|
+
os.path.join(temp_dir, ".agentsecure", "runs", "run_test", AGENT_GUIDE_FILENAME),
|
|
44
|
+
path,
|
|
45
|
+
)
|
|
46
|
+
self.assertTrue(os.path.exists(path))
|
|
47
|
+
self.assertEqual(
|
|
48
|
+
os.path.join(".agentsecure", "runs", "run_test", AGENT_GUIDE_FILENAME),
|
|
49
|
+
relative_agent_guidance_path(temp_dir, path),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
if __name__ == "__main__":
|
|
54
|
+
unittest.main()
|