Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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: 4 additions & 4 deletions .github/workflows/agentshield-loop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6

- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 # stable

- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2
with:
key: agentshield-loop-v0.8.7

Expand All @@ -53,7 +53,7 @@ jobs:

- name: Upload loop state
if: always()
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: agentshield-loop-state
path: docs/loops/agentshield-scan/STATE.md
4 changes: 2 additions & 2 deletions .github/workflows/agentshield.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ jobs:
name: Security scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6

- name: Run AgentShield
uses: aiconnai/agentshield@v0.8.7
uses: aiconnai/agentshield@663cf4bf3c7a633c5caad398105264eb8680679d # v0.8.7
with:
path: .
fail-on: high
Expand Down
230 changes: 159 additions & 71 deletions .github/workflows/ci.yml

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6

- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@641a925cfafe92d0fdf8b239ba4053e3f8d99d6d # v3
with:
languages: ${{ matrix.language }}
config-file: ./.github/codeql/codeql-config.yml

- name: Run CodeQL analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@641a925cfafe92d0fdf8b239ba4053e3f8d99d6d # v3
with:
category: "/language:${{ matrix.language }}"
4 changes: 2 additions & 2 deletions .github/workflows/gitleaks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
name: Secret scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
fetch-depth: 0

Expand All @@ -35,7 +35,7 @@ jobs:

- name: Upload SARIF to GitHub Security
if: always()
uses: github/codeql-action/upload-sarif@v4
uses: github/codeql-action/upload-sarif@1ad29ea4a422cce9a242a9fae469541dcd08addc # v4
with:
sarif_file: gitleaks.sarif
category: gitleaks
4 changes: 2 additions & 2 deletions .github/workflows/harness-contract.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
name: Harness Contract
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5

- name: Harness bootstrap sanity (read-only)
run: bash docs/harness/bin/bootstrap.sh
Expand All @@ -59,7 +59,7 @@ jobs:
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5

- name: Harness doctor (advisory, non-blocking)
continue-on-error: true
Expand Down
40 changes: 20 additions & 20 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ jobs:
name: Fuzz Testing
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@nightly
- uses: Swatinem/rust-cache@v2
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- uses: dtolnay/rust-toolchain@efcb852328a9f50117170cc43094fb6f09eaf1ae # nightly
- uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2

- name: Install cargo-fuzz
run: cargo install cargo-fuzz
Expand All @@ -49,9 +49,9 @@ jobs:
# Only run on Sundays
if: github.event.schedule == '0 3 * * 0' || github.event_name == 'workflow_dispatch'
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 # stable
- uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2

- name: Install cargo-mutants
run: cargo install cargo-mutants
Expand All @@ -61,7 +61,7 @@ jobs:
continue-on-error: true

- name: Upload mutation report
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: mutation-report
path: mutants.out/
Expand All @@ -74,9 +74,9 @@ jobs:
name: Extended Property Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 # stable
- uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2

- name: Run extended property tests
run: cargo test --test property_tests -- --test-threads=1
Expand All @@ -91,11 +91,11 @@ jobs:
name: Miri (Undefined Behavior Check)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@nightly
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- uses: dtolnay/rust-toolchain@efcb852328a9f50117170cc43094fb6f09eaf1ae # nightly
with:
components: miri
- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2

- name: Run Miri on critical modules
run: |
Expand All @@ -110,8 +110,8 @@ jobs:
name: Check Outdated Dependencies
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@stable
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 # stable

- name: Install cargo-outdated
run: cargo install cargo-outdated
Expand All @@ -130,15 +130,15 @@ jobs:
permissions:
contents: write
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 # stable
- uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2

- name: Run all benchmarks
run: cargo bench -- --output-format bencher | tee bench-output.txt

- name: Track benchmark results
uses: benchmark-action/github-action-benchmark@v1
uses: benchmark-action/github-action-benchmark@52576c92bccf6ac60c8223ec7eb2565637cae9ba # v1.22.1
with:
name: Engram Performance (Nightly)
tool: cargo
Expand All @@ -151,7 +151,7 @@ jobs:
benchmark-data-dir-path: dev/bench-nightly

- name: Upload raw Criterion reports
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: nightly-benchmarks
Expand Down
20 changes: 10 additions & 10 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,15 @@ jobs:
target: aarch64-unknown-linux-gnu

steps:
- uses: actions/checkout@v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
ref: ${{ env.RELEASE_VERSION }}

- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 # stable
with:
targets: ${{ matrix.target }}

- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2
with:
key: release-${{ matrix.target }}

Expand Down Expand Up @@ -123,7 +123,7 @@ jobs:
shasum -a 256 "${ARCHIVE}" > "${ARCHIVE}.sha256"

- name: Upload artifact
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: ${{ matrix.target }}
path: |
Expand All @@ -135,12 +135,12 @@ jobs:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
ref: ${{ env.RELEASE_VERSION }}

- name: Download all artifacts
uses: actions/download-artifact@v7
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
with:
path: artifacts

Expand All @@ -157,7 +157,7 @@ jobs:
cat release/RELEASE_NOTES.md

- name: Create GitHub Release
uses: softprops/action-gh-release@v3
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3
with:
tag_name: ${{ env.RELEASE_VERSION }}
draft: false
Expand Down Expand Up @@ -187,20 +187,20 @@ jobs:

- name: Download SHA256 files
if: steps.homebrew-token.outputs.available == 'true'
uses: actions/download-artifact@v7
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
with:
path: artifacts

- name: Checkout Engram release source
if: steps.homebrew-token.outputs.available == 'true'
uses: actions/checkout@v6
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
path: engram-source
ref: ${{ env.RELEASE_VERSION }}

- name: Checkout homebrew-engram
if: steps.homebrew-token.outputs.available == 'true'
uses: actions/checkout@v6
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
repository: aiconnai/homebrew-engram
token: ${{ env.HOMEBREW_TAP_TOKEN }}
Expand Down
18 changes: 5 additions & 13 deletions .github/workflows/semgrep.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,21 @@ permissions:

jobs:
scan:
name: Semgrep (optional)
name: Semgrep
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6

- name: Run Semgrep
run: |
set +e
docker run --rm \
-v "${{ github.workspace }}:/src" \
returntocorp/semgrep:latest \
semgrep --config p/ci --sarif --output /src/semgrep.sarif /src
EXIT_CODE=$?
set -e

if [ "$EXIT_CODE" -gt 1 ]; then
exit $EXIT_CODE
fi
semgrep/semgrep:1.169.0 \
semgrep --config p/ci --error --sarif --output /src/semgrep.sarif /src

- name: Upload SARIF result
if: always()
uses: github/codeql-action/upload-sarif@v4
uses: github/codeql-action/upload-sarif@1ad29ea4a422cce9a242a9fae469541dcd08addc # v4
with:
sarif_file: semgrep.sarif
category: semgrep
16 changes: 14 additions & 2 deletions docs/MCP_AUTH.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,20 @@ Engram can enforce a token-bucket rate limit for MCP HTTP requests:
- `--http-rate-limit-burst` / `ENGRAM_HTTP_RATE_LIMIT_BURST` (default: `240`)
- `--http-rate-limit-key` / `ENGRAM_HTTP_RATE_LIMIT_KEY` (optional identity header)

When the key is unset, bucket keys are derived from `x-forwarded-for`,
then `x-real-ip`, then `ip:unknown`.
When the key is unset, bucket keys use the TCP socket peer address. Forwarded
identity is accepted only when that peer matches a CIDR in
`ENGRAM_HTTP_TRUSTED_PROXIES` (comma-separated IPv4/IPv6 CIDRs). Engram then
normalizes `X-Forwarded-For` from right to left across trusted hops. Malformed,
empty, or overlong chains fall back to the socket peer. `X-Real-IP` is never
trusted implicitly.

Example for a loopback reverse proxy and a private proxy tier:

```bash
ENGRAM_HTTP_TRUSTED_PROXIES="127.0.0.0/8,10.0.0.0/8"
```

Leave the variable unset to disable trusted-proxy mode.

When a key header is set, its value is used as the bucket identity key.

Expand Down
8 changes: 4 additions & 4 deletions docs/harness/.sensors-last
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
status=pass
ci_status=pass
doctor_status=pass
mode=quick
timestamp=2026-07-12T07:40:59Z
duration_sec=30
ci=cargo fmt + cargo check + pr-title-policy + harness doctor
mode=full
timestamp=2026-07-12T17:04:23Z
duration_sec=40
ci=fmt + clippy + test_lib + test_integration + test_integration_watch + wasm_target + wasm_all_targets + wasm_wasm_target + doc + ref_check + pr-title-policy + harness doctor
1 change: 1 addition & 0 deletions docs/harness/.sensors-log
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,4 @@
{"schema_version":"sensors-log-v1","timestamp":"2026-07-09T20:18:57Z","tool":"sensors","mode":"full","status":"pass","duration_sec":30,"ci_status":"pass","doctor_status":"pass","ci_command":"fmt + clippy + test_lib + test_integration + test_integration_watch + wasm_target + wasm_all_targets + wasm_wasm_target + doc + ref_check + pr-title-policy + harness doctor","ci_steps":{"fmt":"pass","clippy":"pass","test_lib":"pass","test_integration":"pass","test_integration_watch":"pass","wasm_target":"pass","wasm_all_targets":"pass","wasm_wasm_target":"pass","doc":"pass","ref_check":"pass"},"exclusion":null,"artifacts":[{"path":"docs/harness/.sensors-last","kind":"sensors_last","format":"key_value"}]}
{"schema_version":"sensors-log-v1","timestamp":"2026-07-10T00:27:38Z","tool":"sensors","mode":"full","status":"pass","duration_sec":89,"ci_status":"pass","doctor_status":"pass","ci_command":"fmt + clippy + test_lib + test_integration + test_integration_watch + wasm_target + wasm_all_targets + wasm_wasm_target + doc + ref_check + pr-title-policy + harness doctor","ci_steps":{"fmt":"pass","clippy":"pass","test_lib":"pass","test_integration":"pass","test_integration_watch":"pass","wasm_target":"pass","wasm_all_targets":"pass","wasm_wasm_target":"pass","doc":"pass","ref_check":"pass"},"exclusion":null,"artifacts":[{"path":"docs/harness/.sensors-last","kind":"sensors_last","format":"key_value"}]}
{"schema_version":"sensors-log-v1","timestamp":"2026-07-12T07:40:59Z","tool":"sensors","mode":"quick","status":"pass","duration_sec":30,"ci_status":"pass","doctor_status":"pass","ci_command":"cargo fmt + cargo check + pr-title-policy + harness doctor","ci_steps":{},"exclusion":null,"artifacts":[{"path":"docs/harness/.sensors-last","kind":"sensors_last","format":"key_value"}]}
{"schema_version":"sensors-log-v1","timestamp":"2026-07-12T17:04:23Z","tool":"sensors","mode":"full","status":"pass","duration_sec":40,"ci_status":"pass","doctor_status":"pass","ci_command":"fmt + clippy + test_lib + test_integration + test_integration_watch + wasm_target + wasm_all_targets + wasm_wasm_target + doc + ref_check + pr-title-policy + harness doctor","ci_steps":{"fmt":"pass","clippy":"pass","test_lib":"pass","test_integration":"pass","test_integration_watch":"pass","wasm_target":"pass","wasm_all_targets":"pass","wasm_wasm_target":"pass","doc":"pass","ref_check":"pass"},"exclusion":null,"artifacts":[{"path":"docs/harness/.sensors-last","kind":"sensors_last","format":"key_value"}]}
28 changes: 28 additions & 0 deletions docs/harness/GATES.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,34 @@ Saídas JSON opt-in para scripts do harness devem seguir
vocabulário de status estável, exit code preservado e nenhum segredo ou dump de
ambiente. O output humano continua sendo o default.

### Required aggregate security gate

The PR-visible `Security Gate` job in `.github/workflows/ci.yml` aggregates
Cargo Audit, Cargo Deny, governed exception-policy validation, CodeQL,
Semgrep, Gitleaks, and AgentShield. Every constituent is release-blocking on a
pull request: a failed or unexpectedly skipped constituent makes the aggregate
red. Event-policy skips are neutral only when explicitly listed in
`tests/fixtures/security_gate_matrix.json`; the current pull-request policy has
no such skip.

Branch protection itself is not changed by repository automation. Instead, the
already-required `Test (ubuntu-latest)` job depends on `security-gate`, so a
security failure blocks that required context transitively. Verify the live,
read-only chain with:

```bash
gh api repos/aiconnai/engram/branches/main/protection/required_status_checks \
> .omo/evidence/task-18-required-contexts.json
python3 scripts/check-security-gate.py \
--matrix tests/fixtures/security_gate_matrix.json \
--required-contexts .omo/evidence/task-18-required-contexts.json \
--workflow .github/workflows/ci.yml
```

The checker also exposes `--self-test-failure` and `--self-test-unrequired` to
prove that a constituent failure or removal of the required-context dependency
fails closed.

### PR Title Policy

Wrapper: `bash docs/harness/bin/pr-title-policy.sh`
Expand Down
12 changes: 8 additions & 4 deletions docs/harness/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,14 @@ No GitHub, o merge em `main` exige estes **required status checks**:
Commits). O `doctor.sh` **não** faz parte deste gate required — ele permanece
local/advisory.

`Security Audit` e `Cargo Deny` rodam nos PRs como sinais **advisory** (ainda não
bloqueiam merge; ver baseline em [`GATES.md`](./GATES.md)). Code review automático
(Copilot / terceiros) é **sinal extra, não autoritativo**: por si só não bloqueia
o merge enquanto não expuser um status check confiável.
`Test (ubuntu-latest)` depende do job PR-visible `Security Gate`, que agrega
Cargo Audit, Cargo Deny, a política de exceções, CodeQL, Semgrep, Gitleaks e
AgentShield. Assim, qualquer falha constituinte bloqueia transitivamente um
contexto já required sem uma escrita automatizada em branch protection.

Code review automático (Copilot / terceiros) é **sinal extra, não
autoritativo**: por si só não bloqueia o merge enquanto não expuser um status
check confiável.

## Estrutura

Expand Down
Loading
Loading