Skip to content

Enterprise security review: remove third-party asset loads, close egress, add trust artifacts - #4972

Merged
vivekchand merged 9 commits into
mainfrom
enterprise/security-review-hardening
Aug 21, 2026
Merged

Enterprise security review: remove third-party asset loads, close egress, add trust artifacts#4972
vivekchand merged 9 commits into
mainfrom
enterprise/security-review-hardening

Conversation

@vivekchand

Copy link
Copy Markdown
Owner

Makes the self-hosted claim true at the edges, and produces the artifacts a vendor security review asks for.

Why

clawmetry/selfhosted.py already documents "a self-hosted deployment makes NO calls to clawmetry.com". That was true of the server — but not of everything around it. Four gaps made the claim false before any daemon opened a socket.

What was wrong

Every dashboard page load contacted two external origins.

  • fonts.googleapis.com (4 head blocks) — discloses every viewer's IP and User-Agent to a third-party processor on each page load; renders in a fallback face on an air-gapped install.
  • cdn.jsdelivr.net (chart.js + date adapter) — third-party JavaScript with DOM access, executing in the page that renders agent transcripts. No SRI, and none possible: jsDelivr's own minifier documents that SRI cannot be used with its dynamically generated files. Both tags sat two lines under a comment reading "Vendored + pinned (no external CDN, no supply-chain risk)".

The suppression gate was keyed wrong. Install telemetry and funnel analytics checked is_custom_endpoint(), true only when CLAWMETRY_ENDPOINT is repointed. A self-hosted server container does not set that — it is the endpoint — so SELF_HOSTED=true alone suppressed nothing in that process.

api.ipify.org was ungated everywhere, buying one cosmetic startup banner line in exchange for telling a third party that this network runs ClawMetry.

Gates failed open. The telemetry gate caught ImportError and fell through to the managed-cloud URL, so a partial install meant a customer's self-hosted server phoned home.

What changed

Assets vendored. 15 woff2 subsets for the dashboard + 13 for the v2 SPA, generated by scripts/vendor_fonts.py — deduped by content hash (60 files to 15, 1.3 MB to 363 KB) with unicode-range preserved, so a Latin-only viewer still fetches ~25 KB. chart.js 4.4.7 and its adapter pulled from the npm registry tarball, not the CDN's re-served copy.

Provenance, not just presence. scripts/vendor.lock.json records package, version, tarball path, license and SHA-256 for all four vendored bundles; scripts/verify_vendor.py re-downloads the registry tarball and byte-compares. All four are byte-identical to their published releases.

Egress closed. New clawmetry.endpoints.egress_suppressed() ORs the three ways an operator declares a deployment private (repointed endpoint, SELF_HOSTED, CLAWMETRY_OFFLINE). Applied to telemetry, funnel analytics, the public-IP lookup, and the unattended-update PyPI check. Every gate now fails closed. The configured push to your own ingest endpoint is untouched — that traffic is the product.

Trust artifacts. docs/EGRESS.md lists every destination, what it sends, when, and how to disable it, with three commands a reviewer can run. SECURITY.md rewritten from 19 lines to a real policy with response targets, scope, supported versions, an honest compliance table, and a deliberate Known Limitations section. RFC 9116 security.txt served by the app itself, so an on-prem instance can answer "who do I report this to?"

Supply chain in CI (.github/workflows/supply-chain.yml, also weekly): external-asset scan, vendor byte-comparison, font staleness check, pip-audit, CycloneDX SBOM, OpenSSF Scorecard to SARIF.

Release provenance. actions/attest-build-provenance signs every artifact. PyPI Trusted Publishing is wired but gated off behind a repo variable — it cannot work until the publisher is configured on PyPI, and this repo auto-publishes on merge.

Found along the way

  • DOMPurify was 3.1.7, which OSV flags for CVE-2025-26791 (mXSS via template literals). Not reachable here — that path needs SAFE_FOR_TEMPLATES:true, which we never set — but scanners flag the version regardless. Upgraded to 3.4.13.
  • .gitignore bug. A duplicate dist/ rule after the v2 un-ignore silently re-ignored clawmetry/static/v2/dist/. Already-tracked files were unaffected, which is why it went unnoticed, but every new file in a rebuilt bundle was being dropped — a CI build from a clean checkout would have shipped an incomplete SPA. The v2 fonts are the first new files to land there.

Testing

  • 47 new tests (test_egress_suppression.py, test_security_txt.py), including that a default managed-cloud install is not suppressed, so the suppression tests prove something.
  • Negative test confirmed: the external-asset guard catches a reintroduced CDN script tag.
  • Verified 28 woff2 + both stylesheets + all four vendor bundles + security.txt ship inside a built wheel.
  • Zero regressions: the affected-area suite (554 tests) was run on this branch and on pristine origin/main; both produce an identical set of 38 pre-existing failures.

Before merging

  1. Create security@clawmetry.comSECURITY.md and security.txt both publish it. A published contact that bounces is worse than none.
  2. clawmetry.com/security currently 404s and /enterprise promises a DPA, pentest summaries and an IR plan that do not exist. Both live in the landing-site repo.

See docs/TRUST_BADGES.md for the follow-on certification plan.

🤖 Generated with Claude Code

@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

@github-actions

github-actions Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Visual diff

Comparing c96ad88f99e0 (head) against the PR base branch.

11 of 66 comparison(s) flagged (>1% pixel diff).

View Before After Diff
desktop overview ⚠️ before after diff · 100.00%
desktop flow ⚠️ before after diff · 1.10%
desktop brain before after diff · 0.80%
desktop usage ⚠️ before after diff · 100.00%
desktop crons ⚠️ before after diff · 2.97%
desktop memory ⚠️ before after diff · 3.48%
desktop security before after diff · 0.17%
desktop subagents ⚠️ before after diff · 1.63%
desktop transcripts before after diff · 0.39%
desktop logs ⚠️ before after diff · 4.70%
desktop skills before after diff · 0.32%
desktop models before after diff · 0.13%
desktop approvals before after diff · 0.22%
desktop alerts before after diff · 0.20%
desktop notifications before after diff · 0.28%
desktop limits before after diff · 0.32%
desktop clusters before after diff · 0.33%
desktop history before after diff · 0.24%
desktop channels before after diff · 0.33%
desktop dives before after diff · 0.28%
desktop harness ⚠️ before after diff · 100.00%
desktop inventory before after diff · 0.33%
desktop nemoclaw before after diff · 0.33%
desktop policy before after diff · 0.33%
desktop selfevolve before after diff · 0.33%
desktop swimlane before after diff · 0.32%
desktop tool-catalog before after diff · 0.32%
desktop tracing before after diff · 0.33%
desktop turn-anatomy before after diff · 0.32%
desktop version-impact before after diff · 0.28%
desktop context-economics before after diff · 0.33%
desktop agents before after diff · 0.32%
desktop evals before after diff · 0.29%
mobile overview ⚠️ before after diff · 100.00%
mobile flow before after diff · 0.01%
mobile brain before after diff · 0.01%
mobile usage before after diff · 0.00%
mobile crons before after diff · 0.02%
mobile memory before after diff · 0.01%
mobile security ⚠️ before after diff · 1.05%
mobile subagents before after diff · 0.02%
mobile transcripts before after diff · 0.00%
mobile logs before after diff · 0.01%
mobile skills before after diff · 0.01%
mobile models before after diff · 0.01%
mobile approvals before after diff · 0.01%
mobile alerts before after diff · 0.00%
mobile notifications before after diff · 0.01%
mobile limits before after diff · 0.01%
mobile clusters before after diff · 0.01%
mobile history before after diff · 0.01%
mobile channels before after diff · 0.02%
mobile dives before after diff · 0.02%
mobile harness before after diff · 0.00%
mobile inventory before after diff · 0.01%
mobile nemoclaw before after diff · 0.02%
mobile policy before after diff · 0.02%
mobile selfevolve ⚠️ before after diff · 1.98%
mobile swimlane before after diff · 0.02%
mobile tool-catalog before after diff · 0.01%
mobile tracing before after diff · 0.01%
mobile turn-anatomy before after diff · 0.00%
mobile version-impact before after diff · 0.01%
mobile context-economics before after diff · 0.01%
mobile agents before after diff · 0.02%
mobile evals before after diff · 0.01%

Folder: c96ad88f99e0. Full PNGs also attached as a workflow artefact.

Generated by visual-diff bot. Pixel diffs >1% flagged; eyeball the table before merging. This check is non-blocking — fail = bot bug, not a code problem.

@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

github-actions Bot pushed a commit that referenced this pull request Aug 17, 2026
@cursor

cursor Bot commented Aug 17, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

github-actions Bot pushed a commit that referenced this pull request Aug 17, 2026
@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

github-actions Bot pushed a commit that referenced this pull request Aug 18, 2026
@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

github-actions Bot pushed a commit that referenced this pull request Aug 19, 2026
@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

github-actions Bot pushed a commit that referenced this pull request Aug 19, 2026
@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

Copy link
Copy Markdown
Owner Author

Blocker: phantom CI cascade from main regression (not a real test failure)

All 30 check runs on this PR complete in 2-6 seconds — including "Python dependency audit", "Vendored fonts not stale", "No third-party asset loads", and all Sync matrix jobs, which normally take 30-120 seconds each. These are phantom failures (runner never assigned, concurrency-cancelled).

Root cause: main CI went red after PR #5008 merged (~2026-08-19T23:53 UTC). E2E Gate (required) is also phantom (3s, no runner).

To unblock:

  1. Find what broke on main after feat(orchestration): workflows + sub-agents fully captured per session — context, replies, live status #5008 merged
  2. Fix main CI → re-trigger this PR

This PR (drift-bot ✅, no merge conflicts) is ready once main CI recovers.


Generated by Claude Code

vivekchand and others added 8 commits August 20, 2026 21:23
Every dashboard page load contacted two external origins. That is three
separate problems, and it made "your data never leaves your network" false
for the self-hosted SKU before any daemon opened a socket:

  fonts.googleapis.com (x4 head blocks)
    Discloses every viewer's IP and User-Agent to a third-party processor on
    every page load, and renders in a fallback face on an air-gapped install.

  cdn.jsdelivr.net (chart.js + date adapter, x2)
    Arbitrary third-party JavaScript with DOM access, executing in the page
    that renders agent transcripts. A CDN compromise is a ClawMetry
    compromise. No SRI was present, and none was possible: jsDelivr's own
    minifier endpoint documents that SRI cannot be used with its dynamically
    generated files. Both tags sat two lines under a comment reading
    "Vendored + pinned (no external CDN, no supply-chain risk)".

Now vendored:
- 15 woff2 subsets for the dashboard, 13 for the v2 SPA, generated by
  scripts/vendor_fonts.py. Deduped by content hash (60 files -> 15; Google
  serves one variable file per subset) and unicode-range is preserved
  verbatim, so a Latin-only viewer still fetches ~25 KB, not the whole set.
- chart.js 4.4.7 + chartjs-adapter-date-fns 3.0.0 from the npm registry
  tarball, not the CDN's re-served copy.

Provenance, not just presence. scripts/vendor.lock.json records package,
version, tarball path, license and sha256 for all four vendored bundles;
scripts/verify_vendor.py re-downloads the registry tarball and byte-compares.
All four are byte-identical to their published releases.

While pinning, DOMPurify was found at 3.1.7, which OSV flags for
CVE-2025-26791 (mXSS via template literals, fixed in 3.2.4). Not reachable
here (that path needs SAFE_FOR_TEMPLATES:true, which we never set) but a
scanner flags the version regardless, so it is upgraded to 3.4.13. marked
stays at 13.0.3, which is advisory-clean; npm latest is 18.x and a 5-major
jump belongs in its own change with transcript-rendering tests.

Also fixes a latent packaging bug found on the way. .gitignore had a
duplicate `dist/` rule after the v2 un-ignore, and .gitignore is
last-match-wins, so clawmetry/static/v2/dist/ was silently re-ignored.
Already-tracked files were unaffected, which is why nobody noticed, but
every NEW file in a rebuilt bundle was being dropped -- a CI build from a
clean checkout would have shipped an incomplete SPA. The v2 fonts are the
first new files to land there, so this had to be fixed to ship them.

The v2 font source of truth is frontend/public/ because vite.config.ts sets
emptyOutDir:true; writing into dist/ alone would be deleted by the next
build. The checked-in bundle is mirrored so it works without a rebuild, and
--check verifies both copies.

Verified: 28 woff2 + both stylesheets + all four vendor bundles are present
in a built wheel, so pip installs ship them.

New CI (.github/workflows/supply-chain.yml, also weekly on cron):
- verify_no_external_assets.py fails on any absolute http(s) reference in a
  fetch-causing tag across 54 served files. Confirmed it catches a
  reintroduced CDN script tag.
- verify_vendor.py hash-pins and byte-compares against npm (--offline for
  air-gapped runners).
- vendor_fonts.py --check fails on a stale or hand-edited stylesheet.
- pip-audit + CycloneDX SBOM artifact, and OpenSSF Scorecard to SARIF.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…nt it all

"Your data never leaves your network" is the whole self-hosted pitch, and a
CISO checks it with tcpdump during the pilot, not by reading the docs. Three
calls could still fire from a deployment that had declared itself private.

The gate was keyed wrong. Both existing suppressions checked
is_custom_endpoint(), which is true only when CLAWMETRY_ENDPOINT is repointed.
A self-hosted SERVER container does not set that -- it *is* the endpoint -- so
SELF_HOSTED=true alone suppressed nothing in that process. New
clawmetry.endpoints.egress_suppressed() ORs the three ways an operator
declares a deployment private: repointed endpoint, SELF_HOSTED, or
CLAWMETRY_OFFLINE.

Fixed:

- api.ipify.org was ungated everywhere. It feeds one cosmetic startup banner
  line, and in exchange discloses to a third party that this network runs
  ClawMetry. Now returns None when suppressed. Both duplicate definitions of
  get_public_ip() are gated; the first is dead (shadowed by the second) but
  gated anyway so reordering cannot reintroduce the call.

- Install telemetry and anonymous funnel analytics now use
  egress_suppressed() instead of is_custom_endpoint().

- The unattended-update PyPI check is skipped for private deployments. On an
  air-gapped network it can only ever time out, and on a monitored one it is
  unexplained egress mid-review. Upgrades there belong to the operator's
  change process.

Every gate fails CLOSED. The pre-existing telemetry code caught an ImportError
and fell through to the managed-cloud URL, so a partial install meant a
customer's self-hosted server phoned home. All three now suppress on error:
the cost of a missing banner line is nothing, the cost of an unexplained
packet in a customer's network is a failed review.

Not changed, and worth saying plainly: the configured push to your own ingest
endpoint is untouched. That traffic is the product.

docs/EGRESS.md is the artifact a security review actually asks for -- every
destination, what it sends, when, and how to disable it, with a per-mode
summary table and three commands the reviewer can run to verify it. Written
from the code, and two claims in the first draft were wrong until the code was
fixed to match (the PyPI check above, and the ipify call).

Verified while writing it: no analytics or error-reporting SDK anywhere in the
shipped tree -- no GA, Segment, Sentry, PostHog, Mixpanel, Amplitude, Hotjar,
FullStory. The one upstream dependency that ships telemetry on by default
(DeepEval -> PostHog) is explicitly disabled before import.

tests/test_egress_suppression.py: 38 cases across all three private modes,
including that a default managed-cloud install is NOT suppressed (so the
suppression tests prove something), truthy/falsy flag parsing, an explicit
operator-chosen telemetry collector still winning, and each gate failing
closed when the check itself raises.

Note: tests/test_flow_runs_endpoint.py has 8 failures, confirmed pre-existing
on origin/main and unrelated to this change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e table

SECURITY.md was 19 lines with no contact address, no response times, no
scope, and no version policy. "Contact the maintainer directly" is not a
disclosure process, and it is the first document a vendor security review
opens. Rewritten to state:

- Where to report (security@clawmetry.com + private GitHub advisory) and what
  happens next: 2 business days to acknowledge, 5 to assess, 30 to fix a
  confirmed critical/high. Framed as targets from a small team rather than a
  contractual SLA, because promising an SLA we cannot staff is the same
  failure as the overclaiming elsewhere.
- 90-day coordinated disclosure, reporters credited by default.
- Supported versions: latest only, no LTS branch, fixes ship forward rather
  than being backported.
- Scope, including what we specifically want reported (anything crossing a
  trust boundary around transcripts, anything reaching the dashboard without
  a credential, anything that gets code onto a node via update/licensing) and
  what is out (runtime bugs, already-rooted host, unexploitable scanner
  output).

The Known Limitations section is deliberate. It names the loopback-without-
credential trust assumption, the bearer token being accepted from a query
parameter, the absence of SSO/SAML/OIDC/SCIM, and the audit log having no
identity to attribute to. A reviewer finds all four in the first hour;
finding them already written down converts "they're hiding things" into
"they know their system". The compliance table says Not started for SOC 2,
ISO 27001/42001 and pen test rather than leaving it implied.

RFC 9116 security.txt served at /.well-known/security.txt by the dashboard
itself, not just the marketing site — an on-prem instance is exactly where a
finder is least likely to know who to tell. Unauthenticated by design: a
vulnerability reporter has no credential. Falls back to an inline contact
rather than 500ing if the file is unreadable, and resolves through the
clawmetry package so it works from a wheel, not just the repo.

tests/test_security_txt.py (9 cases) checks required fields, that Contact is
a real URI scheme, that the route serves 200 as text/plain, and that Expires
is neither past (RFC 9116 says a stale file is invalid — this test is the
renewal alarm) nor absurdly far out. Verified the file ships inside a built
wheel; dotfile directories are easy to lose in packaging.

BEFORE MERGING: create the security@clawmetry.com alias. A published contact
that bounces is worse than none.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ge plan

Releases authenticate to PyPI with a long-lived PYPI_API_TOKEN. A standing
credential that can publish to the index is exactly what a supply-chain
reviewer asks about, and it is also what blocks PEP 740 attestations, which
are only available through Trusted Publishing.

- actions/attest-build-provenance signs every built artifact against the
  workflow run and commit. Anyone can check it:
  `gh attestation verify dist/clawmetry-*.whl --repo vivekchand/clawmetry`.
  Free, and it does not require trusting us about what produced a wheel.

- Trusted Publishing is wired but gated on a PYPI_TRUSTED_PUBLISHING repo
  variable, NOT switched on. It cannot work until the publisher is configured
  on PyPI, and this repo auto-publishes on merge, so flipping it outright
  would break releases. Default path is unchanged and merging this is safe.
  The three switch-over steps are in the workflow comment.

Worth noting for positioning: PyPI provenance is a stronger claim than the
self-issued Ed25519 attestation competitors sell as a paid product. It is
free, standard, and issued by an index we do not control.

README: OpenSSF Scorecard, security policy and egress badges. The Scorecard
badge resolves only after the workflow runs on the default branch with
publish_results, so expect it to be blank until this merges.

docs/TRUST_BADGES.md is the shopping list, prices verified today:
- Free and worth doing: Trusted Publishing, OpenSSF Best Practices (passing
  only -- gold requires two maintainers, so it is out of reach and the doc
  says so), CSA STAR Level 1 CAIQ, CodeQL/Dependabot/private reporting/branch
  protection, SPF+DKIM+DMARC+HSTS.
- Paid, in budget: Cyber Essentials ~GBP 300+VAT, which matters mainly
  because it bundles cyber liability insurance for UK entities under GBP 20m
  turnover -- and an MSA typically demands GBP 2-5M cover, a hard gate no
  engineering clears. FLAGGED as contingent: it needs a UK-registered entity,
  and if we trade through a US LLC it does not apply.
- Explicitly not worth it now: SOC 2, ISO 27001/42001, pen test, CSA
  Valid-AI-ted ($595, over budget, same registry entry as the free listing).
  Start the SOC 2 clock alongside a real pilot, not speculatively.

The doc leads with "badges do not close deals" and ends with three
prerequisites that block harder than any certification: security@ must exist
before the two files publishing it merge; /security 404s; /enterprise still
promises a DPA, pentest summaries and an IR plan that do not exist.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…nedx-py

cyclonedx-bom removed --outfile in the latest release; redirect stdout
instead, which works across all supported versions.
Confirmed with the founder: ClawMetry trades as Instalabs LLC, a Wyoming (US)
entity. That settles the open question in TRUST_BADGES.md and changes what is
worth buying.

RULED OUT -- Cyber Essentials (~GBP 300+VAT) was the previous top paid
recommendation. It certifies UK-registered organisations only, and its
genuinely valuable part -- bundled cyber liability insurance for UK entities
under GBP 20m turnover -- is UK-only as well. A US LLC gets neither.

ADDED, and better suited to a US entity:

- CISA Secure Software Development Attestation. Free, self-attested, gates US
  federal sales and increasingly cascades to federal contractors. Worth doing
  now rather than later because its four attestation areas map almost exactly
  onto what this branch already built: secure build environment, trusted
  source-code supply chain (verify_vendor.py byte-comparison), data provenance
  for internal and third-party code (vendor.lock.json + SBOM), and automated
  vulnerability checking (pip-audit + Scorecard). Filing is mostly
  transcription. Table in the doc maps each area to the artefact.

- Cyber / Tech E&O insurance, priced for the US market rather than assumed
  from the UK bundle: ~$1,500-4,000/yr for bundled cover under ~$2M revenue.
  Above the $500 budget and flagged as such, but listed because an MSA
  routinely demands $2-5M cover -- the single most likely thing to stall a
  signed pilot at the paperwork stage. Get the quote before a deal, bind when
  a contract needs it.

GDPR: the first draft of this said "appoint an Article 27 representative, it
is a legal requirement we are not meeting." That was too confident. Article 27
applies only to controllers NOT established in the Union, and while the entity
is Wyoming, the sole owner-operator is NL-resident -- and establishment turns
on stable arrangements and where activity is actually carried out, not on
where the company is registered. If the business counts as EU-established,
Article 27 does not apply at all, GDPR applies directly under Art. 3(1), and
there is a lead supervisory authority (Dutch AP), which is a better answer to
give an EU buyer than a contracted mailbox -- and free. Rewritten to put the
establishment question first, with both branches costed, because buying a
representative we do not need and skipping one we do are both real failure
modes. Flagged as a lawyer question, not an engineering one.

The thing that actually blocks a deal is having NO answer, so the prerequisite
now reads "settle it and publish the conclusion" rather than "buy this".

SECURITY.md compliance table gains the row honestly ("Being determined"),
names the publishing entity, and points out that self-hosted and air-gapped
deployments sidestep the question entirely because no personal data reaches us.

Also: security@clawmetry.com now exists, so that prerequisite is struck.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vivekchand
vivekchand force-pushed the enterprise/security-review-hardening branch from 8d2a90c to f91a1b4 Compare August 20, 2026 21:23
@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

github-actions Bot pushed a commit that referenced this pull request Aug 20, 2026
@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

github-actions Bot pushed a commit that referenced this pull request Aug 21, 2026
@vivekchand
vivekchand merged commit 07d5272 into main Aug 21, 2026
36 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants