Dependency-light risk linter for autonomous-agent configuration files.
The first MVP scans JSON, YAML, and TOML configs for unsafe capability combinations in agent runtimes: untrusted inputs, private data access, outbound actions, persistence, shell/filesystem/browser access, weak approval gates, weaker model choices, unpinned remote tool sources, runtime package installs, unrestricted network egress, and secrets exposed to dangerous tools.
Agent risk is usually not one setting. The dangerous failures appear when capabilities combine:
untrusted content + private data + outbound action = lethal trifecta
This repo turns that into repeatable checks that can run locally or in CI.
python -m pip install -e .Or run without installing:
PYTHONPATH=src python -m agent_config_linter.cli examples/high-risk-agent.json --format jsonagent-config-lint path/to/agent.json --format json
agent-config-lint path/to/agent.yaml --format markdown
agent-config-lint path/to/config-directory --format github-markdown --summary-only
agent-config-lint path/to/config-directory --format sarif > agent-config-linter.sarif
agent-config-lint path/to/config-directory --baseline agent-config-linter-baseline.json --format json
agent-config-lint path/to/config-directory --policy agent-config-linter-policy.json --format json
agent-config-lint path/to/config-directory --generate-baseline agent-config-linter-baseline.json --format json
agent-config-lint path/to/config-directory --baseline agent-config-linter-baseline.json --fail-on-stale-baseline --format json
agent-config-lint path/to/config-directory --baseline agent-config-linter-baseline.json --fail-on-expired-baseline --format json
agent-config-lint path/to/config-directory --min-severity medium --fail-on high --format json
agent-config-lint --repo-scan path/to/repository --format json
agent-config-lint path/to/agent.yaml --explain ACL-001 --format json
agent-config-lint path/to/agent.yaml --suggestions --format markdown
agent-config-lint --repo-scan path/to/repository --trend-summary --format json
agent-config-lint path/to/config-directory --policy agent-config-linter-policy.json --check-policy-drift --fail-on-policy-drift --format json
agent-config-lint --list-rules --format json
agent-config-lint --list-rules --format markdown
agent-config-lint --integration-manifest --format json
agent-config-lint --report-schema --format json > agent-config-linter-report.schema.json
agent-config-lint --validate-report agent-config-linter-report.json --format json
agent-config-lint --compare-reports before.json after.json --format json
agent-config-lint --compare-reports before.json after.json --fail-on-new --format json
agent-config-lint --compare-reports before.json after.json --format markdown
agent-config-lint --compare-reports before.json after.json --fail-on-new --format github-markdown --summary-only
agent-config-lint --versionOutput includes:
risk_level:low,medium,high, orcriticalscoresignals.lethal_trifectasignals.enabled_capabilities- structured
findings, including stablerule_id,rule_name, additiveconfidence, normalizedevidence_paths, originalsource_evidence_paths, and deterministicfingerprintfields - optional
policy_suppressed_findingsandpolicy_suppressed_summarywhen a policy disables or allowlists findings - optional
suppressed_findingsandsuppressed_summarywhen a baseline is provided - optional
baseline.stale_suppressions/baseline.stale_countfor baseline cleanup - optional
baseline.expired_suppressions/baseline.expired_countandbaseline.owner_summaryfor baseline review - optional
filtered_findings/filtered_summarywhen--min-severityfilters low-priority findings - optional
scandiagnostics when--repo-scanreports discovered files, ignored paths, and parser failures separately from active findings - optional
explanationswhen--explainexpands one active finding into docs, evidence, remediation, and suppression guidance - optional review-only
suggestionson findings when--suggestionsis used - optional
trend_summarywhen--trend-summaryemits compact counts for time-series ingestion - optional
policy_driftwhen--check-policy-driftreports unknown, missing, or stale policy bundle references rule_catalogwhen--list-rulesemits the built-in rule catalog without scanning configs- optional
exit_policywhen--fail-onis used for CI gating recommended_next_actions
Machine-readable JSON reports include schema_version. Additive fields may ship without changing schema_version, but removing or renaming fields, changing finding identity keys, or changing suppression semantics should trigger a schema review. Each generated JSON finding and SARIF result includes a deterministic sha256: finding fingerprint for cross-run comparison; it is derived from the rule ID, normalized report path, and sorted unique evidence paths. Run agent-config-lint --report-schema --format json to retrieve the Draft 2020-12 JSON Schema for report schema_version 0.1; the same contract is committed at docs/report-schema.json. Golden-output tests in tests/test_report_golden.py guard JSON, Markdown, and SARIF report shape; see docs/report-stability.md for the fingerprint identity boundary and before intentionally updating report output.
Formats:
- Config inputs:
.json,.yaml,.yml, and.tomlfiles. - Directory inputs: scanned recursively for supported config files; unsupported files are ignored during directory scans.
- Report output
json: full machine-readable report. - Report output
markdown: human-readable report for PR comments, issues, or chat handoff. - Report output
github-markdown: concise GitHub-flavored Markdown with stable summary and findings sections for PR comments or job summaries. - Report output
sarif: GitHub code scanning compatible report. --summary-only: emits only the concise summary for Markdown-style formats, useful for chat/CI logs.
0: scan completed with no fail-on gate, or stored-report comparison completed successfully regardless of new findings.1: a fail-on gate triggered (--fail-on,--fail-on-stale-baseline,--fail-on-expired-baseline,--fail-on-policy-drift,--fail-on-new, or--explainfound no matching finding), stored-report contract validation failed, or duplicate active fingerprints made a comparison ambiguous.2: a load, JSON, parse, policy, baseline, non-object report, or unsupported-format error occurred.
Wrapper, editor, and dashboard authors can discover supported formats, exit codes, flags, optional report sections, report-schema availability, stored-report validation, and stored-report comparison programmatically with agent-config-lint --integration-manifest --format json. Published representative report payloads for parser validation live in docs/report-contracts/.
Validate an archived report without rescanning source configs:
agent-config-lint --validate-report agent-config-linter-report.json --format jsonValidation is local, dependency-light, and non-executable: it reads one JSON object, enforces the published stable fields and schema_version, accepts additive unknown fields, performs no config/plugin/tool execution or network access, and rejects files larger than 10 MiB. File-report paths and schema-declared arrays are capped at 4,096 characters/items, validation diagnostics at 100 errors, and operational diagnostic text at 512 UTF-8 bytes; truncated diagnostics carry deterministic limit metadata. A valid report exits 0, stable-contract mismatches exit 1, and load/JSON/format errors exit 2.
Compare active findings in two archived reports without rescanning configs:
agent-config-lint --compare-reports before.json after.json --format jsonStored-report comparison securely loads and validates both reports through the same bounded local JSON path, then emits sorted new_findings, persisting_findings, and resolved_findings arrays plus summary counts under an 8 MiB projected/final output budget. It fails closed when active severity summaries disagree, normalized file identities collide, repository discovery metadata is malformed, or discovered files do not agree with reports. The emitted scope is repository only when both reports carry complete scan metadata; two reports without scan metadata are compared as an explicit file-set, and mixed scopes are rejected. Entries include the durable fingerprint, report path, rule and finding IDs, severity, and title; persisting entries prefer metadata from the after report. Genuine legacy schema_version 0.1 reports without later additive fingerprint, confidence, or source-provenance fields are supported by deriving the documented identity without mutating the loaded reports. Duplicate active fingerprints are rejected as ambiguous.
Comparison supports stable JSON plus safely escaped markdown and github-markdown. Add --summary-only to either Markdown format for a compact count-and-gate summary suitable for GITHUB_STEP_SUMMARY; full Markdown includes auditable new, persisting, and resolved tables. Table-breaking pipes/newlines are escaped and @everyone/@here mass mentions are neutralized. The command performs no config scan, plugin/tool execution, arbitrary import, or network access, and exits 0 even when new findings exist. Add --fail-on-new for an explicit CI regression gate: JSON preserves the complete machine-readable comparison and Markdown preserves its human-readable summary on stdout, and exit 1 occurs only when the comparison contains new active findings. Comparison validates structure and canonical identity, not artifact provenance; use integrity-protected or signed reports when an attacker could rewrite stored artifacts consistently.
PYTHONPATH=src python -m agent_config_linter.cli examples/high-risk-agent.json --format json
PYTHONPATH=src python -m agent_config_linter.cli examples/high-risk-agent.yaml --format markdown
PYTHONPATH=src python -m agent_config_linter.cli examples/config-shapes --format github-markdown --summary-only
PYTHONPATH=src python -m agent_config_linter.cli examples/high-risk-agent.toml --format sarif
PYTHONPATH=src python -m agent_config_linter.cli examples/config-shapes --format json
PYTHONPATH=src python -m agent_config_linter.cli examples/high-risk-agent.json --baseline examples/agent-config-linter-baseline.json --format json
PYTHONPATH=src python -m agent_config_linter.cli examples/high-risk-agent.json --policy examples/agent-config-linter-policy.json --format json
PYTHONPATH=src python -m agent_config_linter.cli examples/high-risk-agent.json --generate-baseline /tmp/agent-config-linter-baseline.json --format json
PYTHONPATH=src python -m agent_config_linter.cli examples/high-risk-agent.json --min-severity medium --fail-on high --format json
PYTHONPATH=src python -m agent_config_linter.cli --repo-scan . --format json
PYTHONPATH=src python -m agent_config_linter.cli examples/high-risk-agent.json --explain ACL-001 --format json
PYTHONPATH=src python -m agent_config_linter.cli examples/high-risk-agent.json --suggestions --format markdown
PYTHONPATH=src python -m agent_config_linter.cli --repo-scan . --trend-summary --format json
PYTHONPATH=src python -m agent_config_linter.cli examples/high-risk-agent.json --policy examples/agent-config-linter-policy.json --check-policy-drift --fail-on-policy-drift --format json
PYTHONPATH=src python -m agent_config_linter.cli --list-rules --format json
PYTHONPATH=src python -m agent_config_linter.cli --report-schema --format json
PYTHONPATH=src python -m agent_config_linter.cli --validate-report docs/report-contracts/risky.json --format json
PYTHONPATH=src python -m agent_config_linter.cli --compare-reports docs/report-contracts/clean.json docs/report-contracts/risky.json --format json
PYTHONPATH=src python -m agent_config_linter.cli --compare-reports docs/report-contracts/clean.json docs/report-contracts/risky.json --fail-on-new --format json
PYTHONPATH=src python -m agent_config_linter.cli --compare-reports docs/report-contracts/clean.json docs/report-contracts/risky.json --format markdown
PYTHONPATH=src python -m agent_config_linter.cli --compare-reports docs/report-contracts/clean.json docs/report-contracts/risky.json --fail-on-new --format github-markdown --summary-only
PYTHONPATH=src python -m agent_config_linter.cli --versionCopy one of the adoption workflows from examples/github-actions/ and replace . with the path containing your agent configs:
examples/github-actions/code-scanning.yml: uploads SARIF to GitHub code scanning withcontents: readandsecurity-events: write.examples/github-actions/pr-summary.yml: writes a PR/job summary with read-only default permissions; comments are opt-in because they requirepull-requests: write.examples/github-actions/staged-enforcement.yml: runs a staged CI gate withexamples/policies/staged-ci.yaml,--min-severity medium, and--fail-on high.examples/github-actions/baseline-cleanup.yml: runs scheduled baseline cleanup with stale and expired suppression gates.examples/github-actions/trend-summary-artifact.yml: uploads a compact JSON report withtrend_summaryandpolicy_driftfor metrics ingestion.examples/github-actions/report-comparison.yml: compares two trusted stored reports, writes a safely escaped job summary, uploads the Markdown artifact even when the gate fails, and then enforces--fail-on-new.
Use the example workflow at examples/github-actions/code-scanning.yml to generate SARIF and upload findings to GitHub code scanning:
- name: Generate SARIF report
run: agent-config-lint . --format sarif > agent-config-linter.sarif
- name: Upload SARIF to GitHub code scanning
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: agent-config-linter.sarifFor downstream repos, replace . with the config file or directory path that should be scanned.
Findings include additive confidence metadata (high, medium, or low) so CI users can separate deterministic dangerous configurations from heuristic hints. Most capability and combination rules are high confidence; weak-model naming is medium confidence because model names are contextual.
Use severity gates when impact should drive blocking decisions:
agent-config-lint configs/ --min-severity medium --fail-on high --format jsonUse policy-level min_confidence when false-positive tolerance matters:
{
"min_confidence": "high"
}A min_confidence policy keeps active findings at or above the requested confidence and reports lower-confidence entries under confidence_filtered_findings / confidence_filtered_summary.
Use --policy to adapt default findings to org-specific risk decisions while keeping the linter deterministic without a policy. Policies can be JSON, YAML, or TOML files.
{
"severity_overrides": {
"ACL-001": "medium"
},
"disabled_rules": ["weak_model_risk"],
"min_confidence": "medium",
"allowlists": {
"tools": ["shell"],
"rules": ["ACL-009"],
"paths": [
{
"path": "examples/dev-*.json",
"rule_id": "ACL-001",
"reason": "Development-only shell access is reviewed separately."
}
]
}
}severity_overrides(orseverities) accepts stable rule IDs, rule names, or finding IDs and one ofcritical,high,medium, orlow.disabled_rules(orrule_disables) removes matching findings from active results and reports them underpolicy_suppressed_findings.allowlists.toolssuppresses findings whose evidence points at an allowedtools.<name>path.allowlists.rulessuppresses matching rule IDs, rule names, or finding IDs.allowlists.pathssuppresses matching path globs, optionally narrowed byrule_idorid.min_confidencefilters active findings below the requested confidence (high,medium, orlow) and reports them underconfidence_filtered_findings.
Invalid policy files are rejected before linting with exit code 2. Validation errors include machine-readable field paths such as allowlists.paths[0].rule_id; see docs/policy-schema.md for minimal, staged-adoption, and strict CI examples.
Use --check-policy-drift to report policy bundle governance issues: unknown rule references, covered-rule lists that omit built-in rules, and stale metadata.policy_bundle_version values. Add --fail-on-policy-drift when CI should block policy drift independently from finding severity.
Use --baseline to suppress accepted findings while keeping an audit trail in JSON output. Baselines can be JSON, YAML, or TOML files with a suppressions list:
{
"suppressions": [
{
"path": "examples/high-risk-agent.json",
"rule_id": "ACL-009",
"reason": "Example fixture intentionally uses a weak/local model to demonstrate the rule.",
"owner": "security-team",
"ticket": "SEC-123",
"expires_at": "2026-12-31"
}
]
}Each suppression must include rule_id, finding_id, or id, plus an optional path glob. Matching, unexpired findings are removed from findings and reported under suppressed_findings with suppressed_summary counts. Optional lifecycle metadata includes:
owner: team or person responsible for revisiting the suppression.ticket: tracking issue/change record.expires_at: ISOYYYY-MM-DDdate. Expired suppressions do not match active findings.
Use --generate-baseline path/to/baseline.json to write the current active findings as suppressions with TODO lifecycle metadata. When an existing baseline is supplied, stale suppressions that no longer match any finding are reported under baseline.stale_suppressions; add --fail-on-stale-baseline to return exit code 1 when cleanup is needed. Expired suppressions are reported under baseline.expired_suppressions, and baseline.owner_summary groups active, stale, and expired suppressions by owner.
Copy-pasteable expiration review command:
agent-config-lint configs/ --baseline agent-config-linter-baseline.json --fail-on-expired-baseline --format jsonSee docs/baseline-review.md for owner-review and cleanup workflows.
Use --trend-summary when teams need compact metrics over time. It adds deterministic counts by rule, severity, confidence, adapter, path prefix, baseline state, and owner under trend_summary without removing the normal JSON report:
agent-config-lint --repo-scan . --baseline agent-config-linter-baseline.json --policy examples/policies/staged-ci.yaml --check-policy-drift --trend-summary --format json > agent-config-linter-report.jsonArchive that report with actions/upload-artifact or copy examples/github-actions/trend-summary-artifact.yml for a ready-to-edit GitHub Actions example.
Use github-markdown plus --summary-only when you want a compact PR/job-summary view without noisy empty tables:
agent-config-lint configs/ --format github-markdown --summary-onlyThe example workflow examples/github-actions-pr-summary.yml writes this output to GITHUB_STEP_SUMMARY with read-only default permissions. PR comments are documented as an explicit opt-in because they require granting pull-requests: write.
Use staged severity filters and exit-code gates to adopt the linter without blocking on every low-priority finding immediately:
agent-config-lint configs/ --min-severity medium --fail-on high --format json--min-severity {critical,high,medium,low}keeps only active findings at or above the threshold and reports lower-severity findings underfiltered_findings/filtered_summary.--fail-on {critical,high,medium,low}returns exit code1when remaining active findings meet or exceed the threshold and records the decision underexit_policy.- Validation/config errors still return exit code
2. --fail-on-stale-baselinealso returns exit code1when stale baseline cleanup is needed.--fail-on-expired-baselinereturns exit code1when accepted-risk suppressions have passedexpires_at.
Examples for local adoption are included in:
examples/pre-commit-config.yamlexamples/Taskfile.ymldocs/sample-agent-config-linter.sarifexamples/github-actions-pr-summary.ymlexamples/policies/local-dev.yamlexamples/policies/staged-ci.yamlexamples/policies/strict-ci.yamlexamples/github-actions/code-scanning.ymlexamples/github-actions/pr-summary.ymlexamples/github-actions/staged-enforcement.ymlexamples/github-actions/baseline-cleanup.yml
Package metadata in pyproject.toml includes classifiers, keywords, project URLs, and author metadata for distribution. Version 0.3.0 is the current compatibility point; report schema_version remains 0.1 because confidence/provenance additions are additive. Use agent-config-lint --version to verify installed versions. Tagged releases matching v* run .github/workflows/release.yml, build distributions with python -m build, run scripts/install-smoke.py --skip-build against the built wheel in a clean virtual environment, and publish via PyPI trusted publishing. See CHANGELOG.md, SECURITY.md, docs/report-stability.md, and docs/release-checklist.md before tagging.
The examples/config-shapes/ directory contains representative Hermes, OpenClaw, MCP, and GitHub Actions shapes used by tests and smoke checks:
hermes-discord-shared.yaml: shared Discord/group-chat agent with terminal, files, secrets, persistence, and network egress.hermes-personal-local.yaml: personal local runtime with a local model and broad filesystem access.openclaw-browser-agent.json: OpenClaw-style browser/web agent with private-network browser access.claude-desktop-risky-mcp.json/claude-desktop-safe-mcp.json: MCP server maps that exercise shell/secrets/outbound normalization and the safe read-only boundary.github-actions-agent-risky.yml/github-actions-agent-safe.yml: workflow snippets that exercise write permissions, secrets, unattended shell tools, and a read-only workflow boundary.cursor-risky-agent-settings.json/cursor-safe-agent-settings.json: Cursor-style editor agent settings for shell/filesystem/MCP/secrets/network normalization and safe scoped settings.windsurf-risky-agent-settings.yaml/windsurf-safe-agent-settings.yaml: Windsurf-style editor agent settings for autonomous shell/package-install/network risk and review-only boundaries.langgraph-risky-deployment.yaml/langgraph-safe-deployment.yaml: LangGraph/LangChain deployment snippets for scheduled tools, package installs, secrets, egress, and safe manual review mode.crewai-risky-deployment.yaml/autogen-safe-deployment.yaml: CrewAI/AutoGen-style snippets covering autonomous crew tools, remote MCP sources, secrets, and a safe human-in-the-loop group chat boundary.
Use these fixtures as starting points when adding schema-specific rules or testing integrations.
- Shell/terminal/subprocess enabled
- Broad filesystem access
- Browser access to localhost/LAN/private network
- Untrusted inputs from web/messaging/email/webhooks
- Private data access through files/memory/email/drive/GitHub/secrets/env
- Outbound action/exfiltration through email/messaging/HTTP/GitHub/browser
- Missing approval gates for dangerous actions
- Small/local/uncensored model risk hints
- Lethal-trifecta detection
- Unpinned remote MCP/tool sources
- Runtime package installation
- Unrestricted network egress
- Secret-bearing environment variables exposed to dangerous tools
Beyond single risky toggles, the linter now flags higher-order combinations that commonly turn prompt injection into real-world impact:
prompt_injection_exfiltration_bridge: untrusted inputs combined with code execution, secrets/credentials access, and network egress.unattended_dangerous_tools: autonomous or scheduled runs that can use shell, destructive, or outbound tools without explicit approvals.privileged_infra_control: Docker/Kubernetes/cloud/IaC-style controls combined with credentials and network egress.
These checks are intentionally conservative: they are meant to catch configs that deserve human review before being used in an autonomous runtime.
Findings include stable rule IDs for baselines and CI integrations. Run agent-config-lint --list-rules --format json for a machine-readable built-in catalog, or --format markdown for a table. See docs/rules.md for the current catalog. New built-in rules should follow the registry checklist in docs/rule-registry.md. Third-party rule packs are validated only as non-executable metadata manifests for now; see docs/rule-packs.md.
| Rule ID | Finding ID | Default severity |
|---|---|---|
| ACL-001 | shell_enabled |
high |
| ACL-002 | filesystem_broad_access |
high |
| ACL-003 | browser_private_network |
high |
| ACL-004 | lethal_trifecta |
critical |
| ACL-005 | prompt_injection_exfiltration_bridge |
critical |
| ACL-006 | unattended_dangerous_tools |
critical |
| ACL-007 | privileged_infra_control |
critical |
| ACL-008 | approval_gate_missing |
critical |
| ACL-009 | weak_model_risk |
medium |
| ACL-010 | filesystem_write_access |
high |
| ACL-011 | unpinned_remote_tool_source |
high |
| ACL-012 | runtime_package_install |
high |
| ACL-013 | unrestricted_network_egress |
high |
| ACL-014 | secret_env_to_dangerous_tool |
critical |
The linter normalizes known agent config shapes before applying rules. Reports include schema.adapter so downstream tooling can see which adapter was used.
Supported adapters:
generic: existing directinputs,tools,enabled_toolsets,secrets,memory,approvals, andmodelfields.hermes: nestedhermes.enabled_toolsets,hermes.toolsets,hermes.channels/hermes.bindings,hermes.secrets, andhermes.network.egress.openclaw: nestedopenclaw.browser.enabled,openclaw.browser.allowPrivateNetwork/privateNetwork,openclaw.browser.localhost, andopenclaw.web.openai: OpenAI-compatibletoolsarrays, includingcode_interpreter,computer_use, and function tools whose names imply outbound sends such as email, Slack, Discord, Telegram, HTTP, or webhooks.mcp: Claude Desktop / MCP-stylemcpServersmaps, including server command/arg names that imply shell, filesystem, or outbound tools, plus serverenvas credential access.github_actions: GitHub Actions workflow snippets withjobs,steps, write-capablepermissions,${{ secrets.* }}references, and unattended agent runs.cursor: Cursor-style workspace settings undercursor.agent, including tested tool toggles, filesystem access, approvals, MCP servers, environment/secrets access, and network egress.windsurf: Windsurf-stylewindsurf.agentsettings for shell/HTTP tools, approvals, autonomy/schedules, package installation, environment exposure, and network egress.langgraph/langchain: deployment-snippet configs with explicit tool lists, webhook/scheduled triggers, runtime package installation, network egress, secrets, and approval settings.crewai/autogen: crew/group-chat deployment snippets with explicit agent tool lists, schedules/autonomous mode, MCP server declarations, environment exposure, and network egress.
The framework adapters intentionally enforce a config-only boundary: they parse explicit YAML/JSON snippets and do not perform static analysis of arbitrary Python application code.
Unsupported fields are ignored until they have fixture-backed tests. Add representative configs under examples/config-shapes/ or tests/fixtures/ before expanding adapter behavior.
The first MVP through Phase 11 are complete. Phase 11 shipped deterministic stored-report comparison, the opt-in --fail-on-new CI regression gate, safely escaped human-readable comparison summaries, and a copyable artifact workflow. Phase 10 report validation and durable finding identity shipped a discoverable schema, stored-report validation, and deterministic cross-run finding fingerprints. Earlier phases shipped policy files, baselines, staged CI gates, packaging/release automation, schema adapters, security regression coverage, compatibility testing, manifest-only rule-pack validation, declarative match-spec metadata, precision-boundary fixtures, repository scan diagnostics, finding explanations, review-only remediation suggestions, trend artifacts, policy-drift checks, installed wheel/sdist smoke coverage, extension governance, the examples gallery, and machine-readable discovery contracts. The current roadmap lives in docs/roadmap.md.
Current release-readiness docs:
- docs/release-checklist.md
- docs/report-stability.md
- docs/report-contracts.md
- docs/extension-governance.md
- docs/examples-gallery.md
PYTHONPATH=src python -m unittest discover -s tests -q
python -m compileall -q src testsCI runs ruff, compileall, and pytest on Python 3.11 and 3.12 across Ubuntu, macOS, and Windows.