Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .gitleaks.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Gitleaks configuration — allowlist test files containing deliberately
# fake credentials used by the security rule detector test suite.

[allowlist]
paths = [
'''tests/unit/security/rules/test_credential_detector\.py''',
'''tests/unit/security/test_output_scanner\.py''',
]
Comment on lines +4 to +8

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

What allowlist mechanisms does the current Gitleaks version support for suppressing only specific known test secrets—such as regex-scoped allowlists or inline gitleaks:allow annotations—instead of excluding an entire file?

💡 Result:

Current Gitleaks (v8.x) supports these fine‑grained allowlisting options to suppress specific known test secrets without excluding whole files:

  • Inline suppression comment: add #gitleaks:allow (or equivalent comment syntax in your language) on the same line as the test secret; Gitleaks will ignore that finding. There’s also a CLI flag --ignore-gitleaks-allow to disable honoring these inline allow comments. [1]
  • Per-finding ignore via fingerprint: create a repo-root .gitleaksignore file containing the finding Fingerprint value(s) to suppress only those exact findings. [1]
  • Rule-scoped allowlists ([[rules.allowlists]]): attach one or more allowlists to a specific rule; a finding is ignored if any allowlist matches. Each allowlist can match on commits, paths, regexes, and stopwords, with a configurable condition (OR/AND) and regexTarget scoping (e.g., apply regexes to the extracted secret vs the full match vs the whole line). [2]
  • Global allowlists ([[allowlists]]): same match primitives (commits/paths/regexes/stopwords) and regexTarget scoping, applied at higher precedence than rule allowlists; additionally, you can scope a shared allowlist to only certain rules using targetRules (so it’s not truly global). [2]

Sources: Gitleaks upstream README/config docs. [1][2]

References

  1. Gitleaks README – gitleaks:allow, .gitleaksignore, and related flags: (github.com)
  2. Gitleaks README – rule allowlists, global allowlists, regexTarget, condition, targetRules: (github.com)

Citations:


🏁 Script executed:

#!/bin/bash
# Check for Gitleaks version specification in common places
echo "=== Checking Gitleaks version specification ==="
for file in package.json requirements.txt pyproject.toml .tool-versions Pipfile setup.py go.mod; do
  if [ -f "$file" ]; then
    echo "Found $file:"
    if [[ "$file" == "package.json" ]]; then
      jq '.devDependencies.gitleaks // .dependencies.gitleaks // empty' "$file" 2>/dev/null || grep -i gitleaks "$file"
    else
      grep -i gitleaks "$file"
    fi
  fi
done

echo ""
echo "=== Checking for gitleaks installation ==="
which gitleaks && gitleaks version 2>/dev/null || echo "gitleaks CLI not in PATH"

Repository: Aureliolo/ai-company

Length of output: 264


🏁 Script executed:

#!/bin/bash
# View the complete .gitleaks.toml configuration
echo "=== Full .gitleaks.toml content ==="
if [ -f ".gitleaks.toml" ]; then
  cat -n .gitleaks.toml
else
  echo ".gitleaks.toml not found at root"
  find . -name ".gitleaks.toml" -o -name "gitleaks.toml" 2>/dev/null | head -5
fi

Repository: Aureliolo/ai-company

Length of output: 443


🏁 Script executed:

#!/bin/bash
# Examine the two test files mentioned in the allowlist
echo "=== Checking test files referenced in allowlist ==="
file1="tests/unit/security/rules/test_credential_detector.py"
file2="tests/unit/security/test_output_scanner.py"

for f in "$file1" "$file2"; do
  if [ -f "$f" ]; then
    echo ""
    echo "=== $f (line count: $(wc -l < "$f")) ==="
    # Show file size and first 100 lines to see what's being allowlisted
    head -100 "$f"
  else
    echo "File not found: $f"
  fi
done

Repository: Aureliolo/ai-company

Length of output: 6271


Use inline allowlisting for test credentials instead of blanket file exemptions.

Gitleaks v8.x supports narrower allowlisting mechanisms: add #gitleaks:allow comments on lines with intentional test credentials, or use .gitleaksignore with finding fingerprints. File-wide path allowlisting will suppress any future real secrets accidentally added to these files.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.gitleaks.toml around lines 4 - 8, Remove the broad path allowlist entries
from the [allowlist] block in the gitleaks config and replace them with targeted
allowlisting: delete the two file-wide exemptions in the allowlist and instead
add inline allow comments (`#gitleaks`:allow) on the specific lines in the test
files that contain intentional test credentials or create .gitleaksignore
entries using finding fingerprints for those exact test values; update the
config to no longer suppress entire files so only the explicit, commented lines
or fingerprinted findings are ignored.

2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ src/ai_company/
persistence/ # Operational data persistence — pluggable PersistenceBackend protocol, SQLite initial (§7.6)
observability/ # Structured logging, correlation tracking, log sinks
providers/ # LLM provider abstraction (LiteLLM adapter)
security/ # SecOps agent, approval gates, audit
security/ # SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, risk classifier, action type registry, ToolInvoker security integration
templates/ # Pre-built company templates, personality presets, and builder
tools/ # Tool registry, built-in tools (file_system/, git, sandbox/, code_runner), MCP bridge (mcp/), role-based access
```
Expand Down
24 changes: 18 additions & 6 deletions DESIGN_SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -2169,7 +2169,7 @@ sandboxing:
> code:read, code:write, code:create, code:delete, code:refactor
> test:write, test:run
> docs:write
> vcs:commit, vcs:push, vcs:branch
> vcs:read, vcs:commit, vcs:push, vcs:branch
> deploy:staging, deploy:production
> comms:internal, comms:external
> budget:spend, budget:exceed
Expand Down Expand Up @@ -3034,11 +3034,23 @@ ai-company/
│ │ ├── factory.py # MCPToolFactory (parallel connect)
│ │ ├── models.py # MCP domain models
│ │ └── result_mapper.py # MCP result → ToolExecutionResult mapping
│ ├── security/ # Security & approval (M7, stubs only)
│ │ ├── approval.py # Approval workflow gates (M7) — domain model is in core/approval.py
│ │ ├── secops_agent.py # Security operations agent (M7)
│ │ ├── audit.py # Audit logging (M7)
│ │ └── permissions.py # Permission checking (M7)
│ ├── security/ # Security & approval
│ │ ├── action_type_mapping.py # Default ToolCategory → ActionType mapping
│ │ ├── action_types.py # ActionTypeCategory registry and validation
│ │ ├── audit.py # Append-only AuditLog with configurable eviction
│ │ ├── config.py # SecurityConfig, SecurityPolicyRule, RuleEngineConfig
│ │ ├── models.py # SecurityVerdict, SecurityContext, AuditEntry, OutputScanResult
│ │ ├── output_scanner.py # Post-tool output scanning (credential/secret detection)
│ │ ├── protocol.py # SecurityInterceptionStrategy protocol
│ │ ├── service.py # SecOpsService — meta-agent coordinating security
│ │ └── rules/ # Rule engine and detectors
│ │ ├── engine.py # RuleEngine (soft-allow + hard-deny, fail-closed)
│ │ ├── protocol.py # SecurityRule protocol
│ │ ├── policy_validator.py # Policy list validation rule
│ │ ├── risk_classifier.py # RiskClassifier (ActionType → ApprovalRiskLevel)
│ │ ├── destructive_op_detector.py # Destructive operation detection
│ │ ├── data_leak_detector.py # Credential/secret pattern detection
│ │ └── _utils.py # walk_string_values utility (recursive argument scanning)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
│ ├── budget/ # Cost management
│ │ ├── _optimizer_helpers.py # CostOptimizer shared helper functions
│ │ ├── config.py # Budget configuration models
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ AI Company lets you spin up a virtual organization staffed entirely by AI agents

- **Memory Backend Adapter (M5)** - Memory protocols, retrieval pipeline, org memory, and consolidation are complete; initial Mem0 adapter backend ([ADR-001](docs/decisions/ADR-001-memory-layer.md)) pending; research backends (GraphRAG, Temporal KG) planned
- **CLI Surface** - `cli/` package is placeholder-only
- **Security/Approval System (M7)** - `security/` package is placeholder-only; real authentication (JWT/OAuth), progressive trust, SecOps agent
- **Security/Approval System (M7)** - SecOps agent with rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, risk classifier, and ToolInvoker integration are implemented; real authentication (JWT/OAuth), progressive trust, and approval workflow gates are planned
- **Advanced Product Surface** - web dashboard, HR workflows, and external integrations

## Status
Expand Down
1 change: 1 addition & 0 deletions src/ai_company/config/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,5 @@ def default_config_dict() -> dict[str, Any]:
"api": {},
"sandboxing": {},
"mcp": {},
"security": {},
}
6 changes: 6 additions & 0 deletions src/ai_company/config/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from ai_company.observability.config import LogConfig # noqa: TC001
from ai_company.observability.events.config import CONFIG_VALIDATION_FAILED
from ai_company.persistence.config import PersistenceConfig
from ai_company.security.config import SecurityConfig
from ai_company.tools.mcp.config import MCPConfig
from ai_company.tools.sandbox.sandboxing_config import SandboxingConfig

Expand Down Expand Up @@ -491,6 +492,7 @@ class RootConfig(BaseModel):
api: API server configuration.
sandboxing: Sandboxing backend configuration.
mcp: MCP bridge configuration.
security: Security subsystem configuration.
"""

model_config = ConfigDict(frozen=True)
Expand Down Expand Up @@ -586,6 +588,10 @@ class RootConfig(BaseModel):
default_factory=MCPConfig,
description="MCP bridge configuration",
)
security: SecurityConfig = Field(
default_factory=SecurityConfig,
description="Security subsystem configuration",
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.

@model_validator(mode="after")
def _validate_unique_agent_names(self) -> Self:
Expand Down
42 changes: 33 additions & 9 deletions src/ai_company/core/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,18 +369,42 @@ class CoordinationTopology(StrEnum):


class ActionType(StrEnum):
"""Convenience constants for common approval action types.
"""Two-level action type taxonomy for security classification.

Models typically use ``NotBlankStr`` for ``action_type`` fields, so these
are optional helper constants and custom string values remain valid.
Used by autonomy presets (DESIGN_SPEC §12.2), SecOps validation
(§12.3), tiered timeout policies (§12.4), and progressive trust
(§11.3). Values follow a ``category:action`` naming convention.

Custom action type strings are also accepted by models that use
``str`` for ``action_type`` fields — these enum members are
convenience constants for the built-in taxonomy.
"""

CODE_MERGE = "code_merge"
DEPLOYMENT = "deployment"
BUDGET_SPEND = "budget_spend"
EXTERNAL_COMMUNICATION = "external_communication"
HIRING = "hiring"
ARCHITECTURE_CHANGE = "architecture_change"
CODE_READ = "code:read"
CODE_WRITE = "code:write"
CODE_CREATE = "code:create"
CODE_DELETE = "code:delete"
CODE_REFACTOR = "code:refactor"
TEST_WRITE = "test:write"
TEST_RUN = "test:run"
DOCS_WRITE = "docs:write"
VCS_COMMIT = "vcs:commit"
VCS_PUSH = "vcs:push"
VCS_BRANCH = "vcs:branch"
DEPLOY_STAGING = "deploy:staging"
DEPLOY_PRODUCTION = "deploy:production"
COMMS_INTERNAL = "comms:internal"
COMMS_EXTERNAL = "comms:external"
BUDGET_SPEND = "budget:spend"
BUDGET_EXCEED = "budget:exceed"
ORG_HIRE = "org:hire"
ORG_FIRE = "org:fire"
ORG_PROMOTE = "org:promote"
VCS_READ = "vcs:read"
DB_QUERY = "db:query"
DB_MUTATE = "db:mutate"
DB_ADMIN = "db:admin"
ARCH_DECIDE = "arch:decide"
Comment on lines 371 to +407

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add a migration path for legacy action_type strings.

SecurityContext.action_type still accepts arbitrary str, and RiskClassifier.classify() falls back to MEDIUM for unknown values. Replacing the built-in values outright means older YAML policies or persisted action_type values can keep loading but be silently reclassified instead of matching their intended policy/risk branch. Please normalize legacy values on load or ship an explicit migration with this taxonomy change.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ai_company/core/enums.py` around lines 371 - 406, The change to
ActionType removed/migrated built-in strings but didn’t provide a
migration/normalization path, so older SecurityContext.action_type values may be
loaded and mis-classified by RiskClassifier.classify(); add a normalization
layer that maps legacy string variants to the new ActionType members (e.g., a
dict mapping legacy names -> ActionType constants) and apply it when
SecurityContext instances are constructed/deserialized (or inside
RiskClassifier.classify() before decision logic) so that classify() receives
canonical ActionType values; update references to ActionType in the codebase to
use the normalized value and ensure unknown strings still fall back to the
current MEDIUM behavior.



class MergeOrder(StrEnum):
Expand Down
83 changes: 80 additions & 3 deletions src/ai_company/engine/agent_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,29 @@
EXECUTION_ENGINE_TIMEOUT,
EXECUTION_RECOVERY_FAILED,
)
from ai_company.observability.events.security import SECURITY_DISABLED
from ai_company.providers.enums import MessageRole
from ai_company.providers.models import ChatMessage
from ai_company.security.audit import AuditLog
from ai_company.security.output_scanner import OutputScanner
from ai_company.security.rules.credential_detector import CredentialDetector
from ai_company.security.rules.data_leak_detector import DataLeakDetector
from ai_company.security.rules.destructive_op_detector import (
DestructiveOpDetector,
)
from ai_company.security.rules.engine import RuleEngine
from ai_company.security.rules.path_traversal_detector import (
PathTraversalDetector,
)
from ai_company.security.rules.policy_validator import PolicyValidator
from ai_company.security.rules.protocol import SecurityRule # noqa: TC001
from ai_company.security.rules.risk_classifier import RiskClassifier
from ai_company.security.service import SecOpsService
from ai_company.tools.invoker import ToolInvoker
from ai_company.tools.permissions import ToolPermissionChecker

if TYPE_CHECKING:
from ai_company.api.approval_store import ApprovalStore
from ai_company.budget.coordination_config import ErrorTaxonomyConfig
from ai_company.budget.enforcer import BudgetEnforcer
from ai_company.budget.tracker import CostTracker
Expand All @@ -68,6 +85,8 @@
)
from ai_company.providers.models import CompletionConfig
from ai_company.providers.protocol import CompletionProvider
from ai_company.security.config import SecurityConfig
from ai_company.security.protocol import SecurityInterceptionStrategy
from ai_company.tools.registry import ToolRegistry

logger = get_logger(__name__)
Expand Down Expand Up @@ -108,6 +127,8 @@ def __init__( # noqa: PLR0913
shutdown_checker: ShutdownChecker | None = None,
error_taxonomy_config: ErrorTaxonomyConfig | None = None,
budget_enforcer: BudgetEnforcer | None = None,
security_config: SecurityConfig | None = None,
approval_store: ApprovalStore | None = None,
) -> None:
self._provider = provider
self._loop: ExecutionLoop = execution_loop or ReactLoop()
Expand All @@ -127,6 +148,8 @@ def __init__( # noqa: PLR0913
self._cost_tracker = budget_enforcer.cost_tracker
else:
self._cost_tracker = cost_tracker
self._security_config = security_config
self._approval_store = approval_store
self._recovery_strategy = recovery_strategy
self._shutdown_checker = shutdown_checker
self._error_taxonomy_config = error_taxonomy_config
Expand Down Expand Up @@ -185,7 +208,7 @@ async def run( # noqa: PLR0913
await self._budget_enforcer.check_can_execute(agent_id)
identity = await self._budget_enforcer.resolve_model(identity)

tool_invoker = self._make_tool_invoker(identity)
tool_invoker = self._make_tool_invoker(identity, task_id=task_id)
ctx, system_prompt = self._prepare_context(
identity=identity,
task=task,
Expand Down Expand Up @@ -642,15 +665,69 @@ async def _apply_recovery(
)
return execution_result

def _make_security_interceptor(
self,
) -> SecurityInterceptionStrategy | None:
"""Build the SecOps security interceptor if configured."""
if self._security_config is None:
logger.warning(
SECURITY_DISABLED,
note="No SecurityConfig provided — all security checks skipped",
)
return None
Comment on lines +673 to +678

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fail closed when SecurityConfig is missing.

Returning None here means ToolInvoker runs without a security_interceptor whenever a caller forgets to pass security_config. That makes SecOps opt-in and bypassable instead of a mandatory gatekeeper.

🔒 Minimal fail-closed change
         if self._security_config is None:
-            logger.warning(
+            logger.error(
                 SECURITY_DISABLED,
-                note="No SecurityConfig provided — all security checks skipped",
+                note="SecurityConfig is required when tool execution is enabled",
             )
-            return None
+            msg = "SecurityConfig is required when tool execution is enabled"
+            raise ValueError(msg)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ai_company/engine/agent_engine.py` around lines 672 - 677, The current
check that returns None when self._security_config is None lets ToolInvoker run
without a security_interceptor; change this to fail-closed: when
self._security_config is None, log the warning (leave
logger.warning(SECURITY_DISABLED,...)) but then raise a clear exception (e.g.,
SecurityConfigMissingError or RuntimeError) so creation/usage of ToolInvoker is
blocked, or alternatively instantiate a deny-all interceptor (e.g.,
DenyAllSecurityInterceptor) and assign it to the security_interceptor to ensure
all requests are rejected; update references around self._security_config and
the code path that constructs ToolInvoker/security_interceptor to enforce one of
these behaviours.

if not self._security_config.enabled:
return None

cfg = self._security_config
re_cfg = cfg.rule_engine
policy_validator = PolicyValidator(
hard_deny_action_types=frozenset(cfg.hard_deny_action_types),
auto_approve_action_types=frozenset(cfg.auto_approve_action_types),
)
# Build the detector list respecting config flags.
detectors: list[SecurityRule] = [policy_validator]
if re_cfg.credential_patterns_enabled:
detectors.append(CredentialDetector())
if re_cfg.path_traversal_detection_enabled:
detectors.append(PathTraversalDetector())
if re_cfg.destructive_op_detection_enabled:
detectors.append(DestructiveOpDetector())
if re_cfg.data_leak_detection_enabled:
detectors.append(DataLeakDetector())

Comment on lines +684 to +698

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

custom_policies are accepted in config but never enforced.

This factory only installs PolicyValidator plus the built-in detectors; nothing turns SecurityConfig.custom_policies into executable SecurityRules. As written, user-supplied policies are silently ignored at runtime, which misses one of the PR’s stated acceptance criteria.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ai_company/engine/agent_engine.py` around lines 683 - 697, The config's
custom_policies are never converted into SecurityRule instances and thus
ignored; update the detector construction in the agent_engine (where
PolicyValidator is created and the detectors list is built) to iterate over
cfg.security.custom_policies (or SecurityConfig.custom_policies) and
instantiate/validate each into a SecurityRule (or wrap them in a
CallableRule/CustomPolicyRule) before appending to detectors, ensuring they are
type-checked and ordered (e.g., appended after PolicyValidator) and that any
invalid custom policy raises or logs an error; keep the existing re_cfg feature
flags but ensure custom policies are honored by adding them to the detectors
list used for enforcement.

rule_engine = RuleEngine(
rules=tuple(detectors),
risk_classifier=RiskClassifier(),
config=re_cfg,
)
return SecOpsService(
config=cfg,
rule_engine=rule_engine,
audit_log=AuditLog(),
output_scanner=OutputScanner(),
approval_store=self._approval_store,
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
greptile-apps[bot] marked this conversation as resolved.

def _make_tool_invoker(
self,
identity: AgentIdentity,
task_id: str | None = None,
) -> ToolInvoker | None:
"""Create a ToolInvoker with permission checking, or None."""
"""Create a ToolInvoker with permission checking and security.

Returns None if no tool registry is configured.
"""
if self._tool_registry is None:
return None
checker = ToolPermissionChecker.from_permissions(identity.tools)
return ToolInvoker(self._tool_registry, permission_checker=checker)
interceptor = self._make_security_interceptor()
return ToolInvoker(
self._tool_registry,
permission_checker=checker,
security_interceptor=interceptor,
agent_id=str(identity.id),
task_id=task_id,
)
Comment on lines 712 to +731

Copilot AI Mar 10, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ToolInvoker now supports task_id for SecurityContext/AuditEntry, but AgentEngine only passes agent_id when constructing the ToolInvoker. This means security/audit records created via the engine will miss task_id. Please pass the current task_id into ToolInvoker (and adjust _make_tool_invoker signature/call site accordingly).

Copilot uses AI. Check for mistakes.

def _log_completion(
self,
Expand Down
32 changes: 32 additions & 0 deletions src/ai_company/observability/events/security.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""Security event constants."""

from typing import Final

SECURITY_EVALUATE_START: Final[str] = "security.evaluate.start"
SECURITY_EVALUATE_COMPLETE: Final[str] = "security.evaluate.complete"
SECURITY_RULE_MATCHED: Final[str] = "security.rule.matched"
SECURITY_RULE_ERROR: Final[str] = "security.rule.error"
SECURITY_VERDICT_ALLOW: Final[str] = "security.verdict.allow"
SECURITY_VERDICT_DENY: Final[str] = "security.verdict.deny"
SECURITY_VERDICT_ESCALATE: Final[str] = "security.verdict.escalate"
SECURITY_AUDIT_RECORDED: Final[str] = "security.audit.recorded"
SECURITY_AUDIT_EVICTION: Final[str] = "security.audit.eviction"
SECURITY_OUTPUT_SCAN_START: Final[str] = "security.output_scan.start"
SECURITY_OUTPUT_SCAN_FINDING: Final[str] = "security.output_scan.finding"
SECURITY_ESCALATION_CREATED: Final[str] = "security.escalation.created"
SECURITY_ESCALATION_STORE_ERROR: Final[str] = "security.escalation.store_error"
SECURITY_CONFIG_LOADED: Final[str] = "security.config.loaded"
SECURITY_DISABLED: Final[str] = "security.disabled"
SECURITY_RISK_FALLBACK: Final[str] = "security.risk.fallback"
SECURITY_CREDENTIAL_DETECTED: Final[str] = "security.credential.detected"
SECURITY_PATH_TRAVERSAL_DETECTED: Final[str] = "security.path_traversal.detected"
SECURITY_DESTRUCTIVE_OP_DETECTED: Final[str] = "security.destructive_op.detected"
SECURITY_DATA_LEAK_DETECTED: Final[str] = "security.data_leak.detected"
SECURITY_POLICY_DENY: Final[str] = "security.policy.deny"
SECURITY_POLICY_AUTO_APPROVE: Final[str] = "security.policy.auto_approve"
SECURITY_INTERCEPTOR_ERROR: Final[str] = "security.interceptor.error"
SECURITY_OUTPUT_SCAN_ERROR: Final[str] = "security.output_scan.error"
SECURITY_AUDIT_CONFIG_ERROR: Final[str] = "security.audit.config_error"
SECURITY_SCAN_DEPTH_EXCEEDED: Final[str] = "security.scan.depth_exceeded"
SECURITY_AUDIT_RECORD_ERROR: Final[str] = "security.audit.record_error"
SECURITY_ACTION_TYPE_INVALID: Final[str] = "security.action_type.invalid"
5 changes: 5 additions & 0 deletions src/ai_company/observability/events/tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@
TOOL_FS_GLOB_REJECTED: Final[str] = "tool.fs.glob_rejected"
TOOL_FS_NOOP: Final[str] = "tool.fs.noop"

# ── Security interception events ────────────────────────────────
TOOL_SECURITY_DENIED: Final[str] = "tool.security.denied"
TOOL_SECURITY_ESCALATED: Final[str] = "tool.security.escalated"
TOOL_OUTPUT_REDACTED: Final[str] = "tool.output.redacted"

# ── Subprocess utility events ───────────────────────────────────
TOOL_SUBPROCESS_TRANSPORT_CLOSE_FAILED: Final[str] = (
"tool.subprocess.transport_close_failed"
Expand Down
56 changes: 56 additions & 0 deletions src/ai_company/security/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""Security subsystem — SecOps agent, rule engine, audit, and models.

Public API:

- ``SecOpsService`` — the meta-agent coordinating security.
- ``SecurityConfig`` — top-level security configuration.
- ``SecurityVerdict`` / ``SecurityVerdictType`` — evaluation results.
- ``SecurityContext`` — tool invocation context for evaluation.
- ``AuditEntry`` / ``AuditLog`` — audit recording.
- ``OutputScanResult`` / ``OutputScanner`` — post-tool output scanning.
- ``SecurityInterceptionStrategy`` — protocol for the ToolInvoker.
- ``ActionTypeRegistry`` / ``ActionTypeCategory`` — action taxonomy.
- ``RuleEngine`` / ``SecurityRule`` — rule evaluation.
"""

from ai_company.security.action_types import (
ActionTypeCategory,
ActionTypeRegistry,
)
from ai_company.security.audit import AuditLog
from ai_company.security.config import (
RuleEngineConfig,
SecurityConfig,
SecurityPolicyRule,
)
from ai_company.security.models import (
AuditEntry,
OutputScanResult,
SecurityContext,
SecurityVerdict,
SecurityVerdictType,
)
from ai_company.security.output_scanner import OutputScanner
from ai_company.security.protocol import SecurityInterceptionStrategy
from ai_company.security.rules.engine import RuleEngine
from ai_company.security.rules.protocol import SecurityRule
from ai_company.security.service import SecOpsService

__all__ = [
"ActionTypeCategory",
"ActionTypeRegistry",
"AuditEntry",
"AuditLog",
"OutputScanResult",
"OutputScanner",
"RuleEngine",
"RuleEngineConfig",
"SecOpsService",
"SecurityConfig",
"SecurityContext",
"SecurityInterceptionStrategy",
"SecurityPolicyRule",
"SecurityRule",
"SecurityVerdict",
"SecurityVerdictType",
]
Loading