| name | limacharlie-iac |
| description | Manage LimaCharlie Infrastructure as Code using ext-git-sync compatible repository structure.
Initialize IaC repos, add/remove tenants, manage global and tenant-specific configurations
(D&R rules, outputs, FIM, extensions, etc.), and coordinate with ext-git-sync for deployment.
Supports importing existing rules from tenants and promoting tenant rules to global.
Use when setting up multi-tenant config management, adding orgs to IaC, or managing
detection rules across organizations via git.
|
| allowed-tools | ["Task","Read","Write","Edit","Bash","Glob","Grep","Skill","AskUserQuestion"] |
LimaCharlie Infrastructure as Code Manager
Manage multi-tenant LimaCharlie configurations using git-based Infrastructure as Code, compatible with the ext-git-sync extension.
LimaCharlie Integration
Prerequisites: Run /init-lc to initialize LimaCharlie context.
LimaCharlie CLI Access
All LimaCharlie operations use the limacharlie CLI directly:
limacharlie <noun> <verb> --oid <oid> --output yaml [flags]
For command help and discovery: limacharlie <command> --ai-help
Critical Rules
| Rule | Wrong | Right |
|---|
| CLI Access | Call MCP tools or spawn api-executor | Use Bash("limacharlie ...") directly |
| Output Format | --output json | --output yaml (more token-efficient) |
| Filter Output | Pipe to jq/yq | Use --filter JMESPATH to select fields |
| D&R Rules | Write YAML manually | Use limacharlie ai generate-* + limacharlie dr validate |
| OID | Use org name | Use UUID (call limacharlie org list if needed) |
Overview
This skill helps you manage LimaCharlie organizations using Infrastructure as Code:
┌─────────────────────────────────────────────────────────────────┐
│ IaC REPOSITORY (ext-git-sync compatible) │
│ │
│ org-manifest.yaml ← Friendly name → OID mapping │
│ │
│ hives/ ← GLOBAL configs (all tenants) │
│ ├── dr-general.yaml D&R rules │
│ ├── fp.yaml False positives │
│ ├── outputs.yaml Output destinations │
│ ├── extensions.yaml Extensions to enable │
│ ├── integrity.yaml FIM rules │
│ └── ... │
│ │
│ orgs/ ← PER-TENANT configs │
│ ├── <oid-1>/ │
│ │ ├── index.yaml Includes global + custom │
│ │ └── custom/ Tenant-specific overrides │
│ │ ├── rules.yaml │
│ │ └── fim.yaml │
│ └── <oid-2>/ │
│ └── index.yaml │
│ │
│ exports/ ← Auto-generated by ext-git-sync │
│ └── orgs/... │
└─────────────────────────────────────────────────────────────────┘
Key Concepts
ext-git-sync Compatibility
This skill generates repositories compatible with LimaCharlie's ext-git-sync extension:
- Folder names under
orgs/ are OIDs (required by ext-git-sync)
- Each org has an
index.yaml that includes global and custom configs
- Global configs in
hives/ are shared via relative path includes
Friendly Name Mapping
Since OID folders are hard to read, the skill maintains org-manifest.yaml:
version: 1
orgs:
acme-corp:
oid: 7e41e07b-c44c-43a3-b78d-41f34204789d
description: "Acme Corporation - Production"
added: "2025-11-30"
globex:
oid: a326700d-3cd7-49d1-ad08-20b396d8549d
description: "Globex Industries"
added: "2025-11-30"
You refer to orgs by friendly name; the skill translates to OIDs.
Commands
Initialize Repository
"Set up a new IaC repo" or "Initialize LimaCharlie IaC at /path/to/repo"
Creates a new ext-git-sync compatible repository:
<repo-path>/
├── org-manifest.yaml
├── hives/
│ ├── dr-general.yaml
│ ├── fp.yaml
│ ├── outputs.yaml
│ ├── extensions.yaml
│ ├── integrity.yaml
│ ├── artifact.yaml
│ ├── exfil.yaml
│ ├── resources.yaml
│ └── installation_keys.yaml
├── orgs/
│ └── .gitkeep
├── exports/
│ └── .gitkeep
├── README.md
└── .gitignore
Workflow:
- Create directory structure
- Initialize global config files with
version: 3 headers
- Create empty
org-manifest.yaml
- Initialize git repository
- Provide instructions for ext-git-sync setup
Add Existing Tenant
"Add tenant acme-corp" or "Add org Acme Corporation to IaC"
Adds an existing LimaCharlie organization to the repository:
Workflow:
- Look up organization by name using
limacharlie org list --output yaml
- Confirm with user if multiple matches
- Create
orgs/<oid>/index.yaml with global includes
- Create
orgs/<oid>/custom/ directory for future customizations
- Add entry to
org-manifest.yaml
- Optionally: Export current config from LC using ext-git-sync or
limacharlie sync pull
- Commit changes
Example index.yaml generated:
version: 3
include:
- ../../hives/extensions.yaml
- ../../hives/dr-general.yaml
- ../../hives/fp.yaml
- ../../hives/outputs.yaml
- ../../hives/integrity.yaml
- ../../hives/artifact.yaml
- ../../hives/exfil.yaml
- ../../hives/resources.yaml
- ../../hives/installation_keys.yaml
Create New Organization
"Create new org called acme-corp" or "Create tenant Acme Corporation in US region"
Creates a new organization in LimaCharlie AND adds it to the repository:
Workflow:
- Use
create_org to create organization in LC
- Follow "Add Existing Tenant" workflow
- Provide installation key information
Remove Tenant from IaC
"Remove acme-corp from IaC" (does NOT delete the org in LC)
Workflow:
- Look up OID from
org-manifest.yaml
- Remove
orgs/<oid>/ directory
- Remove entry from
org-manifest.yaml
- Commit changes
Rule Management
Add Global Rule (New)
"Add detection for encoded PowerShell to all tenants"
"Create global rule to detect mimikatz"
Creates a NEW rule and adds it to global config:
Workflow:
- Use AI generation (
limacharlie ai generate-detection, limacharlie ai generate-response)
- Validate with
limacharlie dr validate
- Append to
hives/dr-general.yaml
- Commit with descriptive message
Import Rule from Tenant
"Import rule encoded-powershell from acme-corp"
"Get rule mimikatz-detection from globex into IaC"
Fetches an EXISTING rule from a LimaCharlie tenant and adds it to the IaC repo:
Workflow:
- Look up tenant OID from
org-manifest.yaml
- Fetch rule using the CLI:
limacharlie dr get --key encoded-powershell --oid <tenant-oid> --output yaml
- Ask user: Add as global (all tenants) or tenant-specific?
- If global: Add to
hives/dr-general.yaml
- If tenant-specific: Add to
orgs/<oid>/custom/rules.yaml
- Commit changes
Promote Rule to Global
"Promote rule encoded-powershell from acme-corp to global"
"Make rule X from globex apply to all tenants"
Takes an existing rule from ONE tenant and makes it apply to ALL tenants:
Workflow:
- Look up source tenant OID from
org-manifest.yaml
- Fetch rule using
limacharlie dr get --key <name> --oid <oid> --output yaml
- Add rule to
hives/dr-general.yaml
- Ask user: Remove from tenant's custom config? (if it was tenant-specific)
- If yes: Remove from
orgs/<oid>/custom/rules.yaml
- Commit: "Promote rule [name] from [tenant] to global"
Example:
User: "Promote rule lateral-movement-psexec from acme-corp to global"
Skill:
1. Fetches rule from acme-corp (OID: 7e41e07b-...)
2. Adds to hives/dr-general.yaml:
hives:
dr-general:
lateral-movement-psexec:
data:
detect: ...
respond: ...
usr_mtd:
enabled: true
3. All tenants now get this rule via their index.yaml includes
Copy Rule Between Tenants
"Copy rule X from acme-corp to globex"
"Give globex the same custom-detection rule that acme-corp has"
Copies a rule from one tenant to another (without making it global):
Workflow:
- Look up source tenant OID
- Fetch rule from source tenant
- Look up destination tenant OID
- Add to
orgs/<dest-oid>/custom/rules.yaml
- Update destination's
index.yaml to include custom rules if needed
- Commit changes
Add Tenant-Specific Rule
"Add custom detection only for acme-corp"
"Create rule for globex to detect their specific app"
Creates a NEW rule for ONE tenant only:
Workflow:
- Look up tenant OID from
org-manifest.yaml
- Use AI generation for rule
- Validate rule
- Create/update
orgs/<oid>/custom/rules.yaml
- Update
orgs/<oid>/index.yaml to include custom rules
- Commit changes
List Rules
"Show all global rules"
"What rules does acme-corp have?"
"List custom rules for globex"
Displays rules from the IaC repo:
Global Rules (hives/dr-general.yaml)
════════════════════════════════════
- encoded-powershell-execution (enabled)
- mimikatz-command-line (enabled)
- lateral-movement-psexec (enabled)
Tenant: acme-corp (7e41e07b-...)
Custom Rules (orgs/.../custom/rules.yaml)
─────────────────────────────────────────
- acme-specific-app-detection (enabled)
Tenant: globex (a326700d-...)
Custom Rules: (none)
Configuration Management
Add Global Configuration
"Add global output to send detections to Slack"
"Enable Zeek extension for all orgs"
"Add FIM rule to watch /etc/passwd globally"
Adds configuration that applies to ALL tenants:
Supported global config types:
| Type | File | Command Example |
|---|
| D&R Rules | hives/dr-general.yaml | "Add detection for X" |
| False Positives | hives/fp.yaml | "Add FP rule for Y" |
| Outputs | hives/outputs.yaml | "Add Slack output" |
| Extensions | hives/extensions.yaml | "Enable Zeek extension" |
| FIM | hives/integrity.yaml | "Add FIM for /etc/passwd" |
| Artifact Collection | hives/artifact.yaml | "Collect auth.log" |
| Exfil Watch | hives/exfil.yaml | "Watch for large uploads" |
| Resources | hives/resources.yaml | "Add payload X" |
| Installation Keys | hives/installation_keys.yaml | "Add Windows install key" |
Add Tenant-Specific Configuration
"Add custom FIM for acme-corp to watch /opt/app"
"acme-corp needs a custom Slack output"
Adds configuration specific to ONE tenant:
Workflow:
- Look up OID from
org-manifest.yaml
- Determine config type
- Create/update appropriate file in
orgs/<oid>/custom/
- Update
orgs/<oid>/index.yaml to include it
- Commit changes
Import All Rules from Tenant
"Import all rules from acme-corp"
"Bootstrap IaC from globex's current config"
Imports ALL D&R rules from a tenant into the IaC repo:
Workflow:
- Look up tenant OID
- Fetch all rules using
limacharlie dr list --oid <oid> --output yaml and limacharlie dr get --key <name> --oid <oid> --output yaml
- Ask user: Add as global or tenant-specific?
- Add rules to appropriate location
- Commit: "Import N rules from [tenant]"
Repository Operations
List Tenants
"Show tenants in IaC" or "List orgs"
IaC Repository Tenants
══════════════════════
Friendly Name OID Custom Configs
─────────────────────────────────────────────────────────────────────
acme-corp 7e41e07b-c44c-43a3-b78d-41f34204789d rules, fim
globex a326700d-3cd7-49d1-ad08-20b396d8549d (none)
initech cb639126-e0bc-4563-a577-2e559c0610b2 outputs
Total: 3 tenants
Show Repository Structure
"Show IaC structure" or "What's in the repo?"
Displays the current repository layout with file summaries.
Validate Repository
"Validate IaC repo" or "Check for errors"
Validates the repository structure and configurations:
Checks:
- All
index.yaml files have valid includes
- Include paths resolve to existing files
- YAML syntax is valid
- D&R rules pass validation
- No orphaned org folders (not in manifest)
- No missing org folders (in manifest but no folder)
Sync from LimaCharlie
"Sync acme-corp from LC" or "Pull current config for globex"
Exports current configuration from LimaCharlie into the repository:
Workflow:
- Look up OID from manifest
- Use ext-git-sync export OR
limacharlie sync pull
- Place exported config in
exports/orgs/<oid>/
- Optionally: Merge into main org folder
- Show diff if merging
Deploy (Local)
"Deploy acme-corp" or "Push configs to LC"
Deploys configuration to LimaCharlie using the CLI:
limacharlie sync push \
--config-file ./orgs/<oid>/index.yaml \
--oid <oid> \
--force \
--hive-dr-general \
--hive-fp \
--outputs \
--integrity \
--artifact \
--exfil \
--resources \
--extensions \
--installation-keys
Note: For production, recommend using ext-git-sync's recurring sync.
D&R Rule Generation
CRITICAL: Never write D&R YAML manually. Always use AI generation.
When creating NEW detection rules:
limacharlie ai generate-detection --description "..." --oid <oid> --output yaml
limacharlie ai generate-response --description "..." --oid <oid> --output yaml
cat > /tmp/detect.yaml << 'EOF'
<detection_yaml>
EOF
cat > /tmp/respond.yaml << 'EOF'
<response_yaml>
EOF
limacharlie dr validate --detect /tmp/detect.yaml --respond /tmp/respond.yaml --oid <oid>
When IMPORTING existing rules from LC, fetch them via API - no generation needed.
ext-git-sync Setup Guide
After initializing the repo and adding tenants, each org needs ext-git-sync configured:
Per-Organization Setup
-
Subscribe to ext-git-sync extension in each org:
limacharlie extension subscribe --name ext-git-sync --oid <oid> --output yaml
-
Create SSH deploy key:
ssh-keygen -t ed25519 -C "lc-gitsync-<org-name>" -f ~/.ssh/lc-gitsync
-
Add public key to GitHub (Settings → Deploy keys → Allow write access)
-
Store private key in LC Secret Manager for each org:
limacharlie secret set --key git-sync-ssh-key --input-file /tmp/ssh-key.txt --oid <oid>
-
Configure ext-git-sync using the exact config schema below
ext-git-sync Config Schema
CRITICAL: Use these exact field names when configuring ext-git-sync:
repo_url: "git@github.com:your-org/your-repo.git"
branch: "main"
conf_root: "orgs/<oid>/index.yaml"
ssh_key_source: "secret"
ssh_key_secret_name: "git-sync-ssh-key"
CLI Example:
cat > /tmp/ext-git-sync-config.yaml << 'EOF'
repo_url: "git@github.com:your-org/your-repo.git"
branch: "main"
conf_root: "orgs/<oid>/index.yaml"
ssh_key_source: "secret"
ssh_key_secret_name: "git-sync-ssh-key"
EOF
limacharlie extension config-set --name ext-git-sync --input-file /tmp/ext-git-sync-config.yaml --oid <org-oid>
Shared SSH Key Option
For MSSP scenarios, you can use ONE deploy key across all orgs:
- Create single SSH key
- Add to GitHub repo
- Store same private key in each org's Secret Manager (same secret name)
- Configure ext-git-sync in each org pointing to same repo
Verify ext-git-sync Setup
After configuration, verify the setup is working:
-
Check extension subscription:
limacharlie extension list --oid <oid> --output yaml
-
Verify secret exists:
limacharlie secret list --oid <oid> --output yaml
-
Check extension config:
limacharlie extension config-get --name ext-git-sync --oid <oid> --output yaml
-
Check for org errors:
limacharlie org errors --oid <oid> --output yaml
-
Trigger manual sync (optional):
- In LC UI: Extensions → ext-git-sync → "Sync Now"
- Check org errors afterward for any issues
Repository Layout Reference
my-lc-iac/
├── org-manifest.yaml # Friendly name → OID mapping
│
├── hives/ # Global configurations
│ ├── dr-general.yaml # Detection rules
│ ├── fp.yaml # False positive rules
│ ├── outputs.yaml # Output destinations
│ ├── extensions.yaml # Extensions to enable
│ ├── integrity.yaml # FIM rules
│ ├── artifact.yaml # Artifact collection
│ ├── exfil.yaml # Exfil monitoring
│ ├── resources.yaml # Resources/payloads
│ └── installation_keys.yaml # Sensor install keys
│
├── orgs/ # Per-tenant configurations
│ ├── 7e41e07b-...-789d/ # acme-corp (OID)
│ │ ├── index.yaml # Includes global + custom
│ │ └── custom/ # Tenant-specific
│ │ ├── rules.yaml
│ │ └── fim.yaml
│ │
│ └── a326700d-...-549d/ # globex (OID)
│ └── index.yaml
│
├── exports/ # ext-git-sync exports land here
│ └── orgs/
│ └── ...
│
├── README.md
└── .gitignore
YAML Structure Reference
hives/dr-general.yaml
version: 3
hives:
dr-general:
rule-name-here:
data:
detect:
event: NEW_PROCESS
op: contains
path: event/COMMAND_LINE
value: "-enc"
respond:
- action: report
name: encoded-powershell
usr_mtd:
enabled: true
expiry: 0
tags: []
hives/extensions.yaml
version: 3
extensions:
- ext-infrastructure
- ext-velociraptor
- ext-reliable-tasking
hives/outputs.yaml
version: 3
outputs:
slack-alerts:
for: detect
module: slack
slack_api_token: hive://secret/slack-token
slack_channel: "#security-alerts"
hives/integrity.yaml (FIM)
version: 3
integrity:
ssh-keys:
patterns:
- /root/.ssh/authorized_keys
- /home/*/.ssh/authorized_keys
platforms:
- linux
tags: []
hives/installation_keys.yaml
version: 3
installation_keys:
windows:
desc: "Windows endpoints"
tags:
- windows
linux:
desc: "Linux servers"
tags:
- linux
org-manifest.yaml
version: 1
orgs:
acme-corp:
oid: 7e41e07b-c44c-43a3-b78d-41f34204789d
description: "Acme Corporation - Production"
added: "2025-11-30"
globex:
oid: a326700d-3cd7-49d1-ad08-20b396d8549d
description: "Globex Industries"
added: "2025-11-30"
Best Practices
1. Git Workflow
- Use branches for changes
- Review via PR before merging to main
- Let ext-git-sync handle deployment from main branch
2. Rule Naming
Use consistent naming: [category]-[description]
encoded-powershell-execution
mimikatz-command-line
lateral-movement-psexec
3. Tenant Customization
- Keep global configs as defaults
- Only add tenant-specific configs when truly needed
- Document why custom configs exist
4. Secrets
- Never commit plaintext secrets
- Use LC Secret Manager:
hive://secret/secret-name
- Reference secrets in configs, don't embed values
5. Testing Changes
- Use
limacharlie sync push --dry-run before deploying
- Test on one org before rolling out globally
- Use
detection-engineering skill to test rules
6. Importing vs Creating
- Import when rule already exists and works well in LC
- Create when building new detection logic
- Promote when a tenant-specific rule should be global
Troubleshooting
General Issues
| Issue | Solution |
|---|
| Include path not found | Check relative path from index.yaml location |
| YAML syntax error | Validate with python -c "import yaml; yaml.safe_load(open('file.yaml'))" |
| Org not in manifest | Run "add tenant" command |
| Rule not appearing | Check enabled: true in usr_mtd |
| Rule exists in LC but not IaC | Use "import rule" command |
ext-git-sync Specific Issues
| Issue | Cause | Solution |
|---|
| "repo_url is required" | Wrong field name | Use repo_url, not repository |
| "ssh_key is required" | Secret doesn't exist or wrong name | Verify secret exists with limacharlie secret list --oid <oid> --output yaml |
| "conf_root not found" | Wrong path in config | Use full path: orgs/<oid>/index.yaml |
| SSH auth failure | Deploy key not added or wrong key | Verify public key is in GitHub deploy keys |
| "Host key verification failed" | First connection to GitHub | Add GitHub to known_hosts or use ssh -o StrictHostKeyChecking=no |
| Sync runs but no changes | Branch mismatch | Verify branch field matches your repo's default branch |
| Extension not in list | Not subscribed | Run limacharlie extension subscribe --name ext-git-sync --oid <oid> |
Debugging ext-git-sync
-
Check org errors first:
limacharlie org errors --oid <oid> --output yaml
This shows recent errors from ext-git-sync including SSH failures and config issues.
-
Verify the complete config:
limacharlie extension config-get --name ext-git-sync --oid <oid> --output yaml
Ensure all required fields are present: repo_url, branch, conf_root, ssh_key_source, ssh_key_secret_name
-
Test SSH key locally:
ssh -i ~/.ssh/your-key -T git@github.com
Should return: "Hi username! You've successfully authenticated..."
-
Verify GitHub deploy key permissions:
- Must have "Allow write access" checked if using bidirectional sync
- Key must be added to the specific repository, not account-level
Command Quick Reference
| Command | Example |
|---|
| Initialize repo | "Set up IaC repo at ~/lc-config" |
| Add tenant | "Add tenant acme-corp" |
| Create tenant | "Create new org called acme-corp" |
| Add global rule | "Add detection for encoded PowerShell" |
| Import rule | "Import rule X from acme-corp" |
| Promote rule | "Promote rule X from acme-corp to global" |
| Copy rule | "Copy rule X from acme-corp to globex" |
| Add tenant rule | "Add custom rule for acme-corp only" |
| List rules | "Show global rules" |
| List tenants | "Show tenants in IaC" |
| Validate | "Validate IaC repo" |
| Deploy | "Deploy acme-corp" |
| Sync | "Sync acme-corp from LC" |
Related Skills
| Skill | Use Case |
|---|
detection-engineering | Test and refine D&R rules before adding to IaC |
lookup-lc-doc | Reference D&R syntax and operators |
reporting | Generate reports across managed orgs |