Skip to content

📖 Add Hive (Hive Commons) security self-assessment - #2286

Open
clubanderson wants to merge 7 commits into
cncf:mainfrom
clubanderson:docs/hive-security-self-assessment
Open

clubanderson wants to merge 7 commits into
cncf:mainfrom
clubanderson:docs/hive-security-self-assessment

Conversation

@clubanderson

@clubanderson clubanderson commented Aug 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds the CNCF TAG-Security self-assessment for Hive under projects/hive-commons/security-assessment/.

Updated 2026-09-09 — new home. This PR originally filed the assessment under projects/kubestellar/sub-projects/hive/. Hive has since been transferred out of the KubeStellar org into its own vendor-neutral org, hivecommons (2026-09-03, full history preserved; old URLs redirect), and is applying to Sandbox as an independent project rather than as a KubeStellar subproject. The file has been moved to projects/hive-commons/security-assessment/self-assessment.md and all kubestellar/hive references rewritten accordingly. KubeStellar consensus for the separation was recorded and closed in kubestellar/kubestellar#3986.

KubeStellar is still named in the document where it remains factually correct — as Hive's origin and as its first production adopter — but it is no longer described as Hive's host org.

What Hive is, and why the threat model is unusual

Hive orchestrates fleets of AI coding agents that autonomously maintain software projects — filing issues, opening pull requests, reviewing, and at high autonomy levels merging. Agents run as CLI subprocesses under tmux, in containers or pods, holding GitHub App or PAT credentials.

That means the system runs model output with credentials that can write to source repositories, so the assessment focuses on the boundaries that contain that rather than on a conventional network/data perimeter.

Covered:

  • ACMM autonomy levels (L1–L6) and the agent mode ladder — the primary risk gate. Write and merge capability is earned by level; low-level agents can only advise.
  • ioscan — prompt-injection scanning of untrusted input on the kick path, with canary support
  • MITM proxy — network egress enforcement (ADR-0002)
  • GitHub App credential handling — JWT→installation token, per-cluster private keys at 0600, distinct from the OAuth/OIDC login path
  • Append-only audit log — the attribution substrate, with rotation and a gzip-bomb read cap
  • Private vulnerability reporting via GitHub Security Advisories

Weaknesses are stated, not omitted

A self-assessment that claims everything is fine is not useful to a reviewer, so the document names its own gaps. The three most significant:

  1. ioscan.fail_mode defaults to open. A scanner or classifier outage degrades toward continuing to process untrusted input rather than blocking — a deliberate availability/security tradeoff, documented as such.
  2. Agents share a container rather than being sandboxed per run, separated by UID rather than process/kernel containment, and can hold live write credentials in-session. Tracked upstream as hivecommons/hive#2804.
  3. Single-maintainer security response capacity, with CODEOWNERS present but not enforced by branch protection — a consequence of the project's own automation merging green PRs. This is the largest institutional risk identified.

It also records plainly that no third-party security audit or penetration test has been performed, and that SBOM/provenance attestations are currently disabled on image builds (with the reason, which is a real container-runtime exec failure rather than an oversight).

Notes for reviewers

  • Relative links in the source document have been rewritten to absolute Hive URLs so they resolve from this repository. All 11 were verified to return 200 against hivecommons/hive@v4 after the org move; one pre-existing broken path (docs/landscape.md → src/docs/landscape.md) is fixed here.
  • The canonical copy lives in the Hive repo; the document says so, so corrections land in one place.
  • Scope is Hive only — not KubeStellar Core, and not Console.
  • If the TOC would prefer a different directory name than hive-commons (for example hive, or holding the file until the Sandbox application is decided), say the word and I will move it.

References

@clubanderson
clubanderson requested a review from a team as a code owner August 27, 2026 16:12

@JustinCappos JustinCappos left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This needs some work to deal with parts the LLM didn't have the data for. There is also some low-hanging fruit the project could do to improve security.

Comment on lines +101 to +104
- Agents read GitHub issues, PRs, comments, labels, and diffs (untrusted text
boundary).
- Agents are "kicked" (launched) with a constructed prompt built from that
text plus repository/task context.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

How do you deal with a party who creates a malicious issue, comment, etc.? I think you're talking about this in the run-pipeline.sh and operator-selected ACMM level, but I don't see the big picture. If I deploy this, what should I worry about?

@clubanderson clubanderson Sep 11, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fair — the document described controls without ever stating the attack path, which meant a reader had to assemble the threat model themselves. Added a section that does it directly: "If you deploy this, what should you worry about?"

It opens with the uncomfortable part rather than burying it:

Anyone with a GitHub account can open an issue or comment on a governed public repository. That text is enumerated by the pre-kick pipeline, scanned by ioscan, and — if it survives — placed into a prompt handed to a CLI agent that holds a real, if scoped, GitHub credential. There is no step in that chain where a human necessarily reads the attacker's text first.

Then a table of concern → what actually stands in the way → what does not. The short version for your specific question about a malicious issue:

  • Merge of malicious code — stopped by the proxy hard-denying PUT /pulls/{n}/merge for every ACMM mode, plus the GraphQL merge mutation. The agent holds no credential path to merge at any autonomy level. ioscan is explicitly not what stops this; a clever prompt beats a scanner, but it does not beat a rule table the model never sees.
  • Backdoored PR — POST /pulls likewise hard-denied for every mode; creation goes through hive-open-pr as the App bot, attribution-stamped and audit-logged. But nothing prevents the content of a legitimately-created PR from being attacker-influenced, so review it like any other PR.
  • Secret exfiltration — mode-tiered tokens mean low-tier agents hold no code-write credential. Canaries detect prompt contents reaching outbound writes — with real limits, see the reply on your redaction comment.

I also added the three settings that actually determine exposure (ACMM level, whether the repo is public, and whether canaries/fail_mode: closed are on — both default permissive), and stated the worst realistic outcome plainly: at L6, an attacker-influenced PR authored and merged by the App bot, traceable and recoverable via attribution trailer and audit log, but still a real bad day. The guidance is to start at L1–L3 and not to start at L6 on a public flagship repo. L6 (full automation with merging) is not the goal for all repos that use hive. Hive can be tuned, at any level, to work alongside any number of maintainers to mature a codebase beyond what would be normally possible with the same quantity of maintainers.

Comment on lines +199 to +209
2. **`ioscan` untrusted-input scanner**
(`src/pkg/ioscan`, `src/docs/ioscan.md`). Scans GitHub issue/PR/label/
author/comment text before it enters an agent kick. Deterministic rules
(Unicode-steganography normalization, base64 decode-and-rescan,
prompt-injection phrasing, dangerous-directive and secret-shape detection)
are always the floor when enabled; blocked text is replaced with a visible
`[ioscan: content withheld — ...]` marker rather than silently dropped or
passed raw. An optional LLM-judge classifier
(`ioscan.classifier.enabled`, default `false`) adds semantic
plain-English-injection detection on top. **Enabled by default**
(`ioscan.enabled: true` is the default per `ioscan.md:9`).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

have you red teamed to know how well this works? What should a potential user be aware of here? Is this perfect defense, works decently well, a partial mitigation, etc.?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No, it has not been red-teamed, and there is no measured detection rate. I searched the repository for any adversarial evaluation artifact and found nothing — src/pkg/ioscan has 56 unit tests across its rule, Unicode, classifier and canary paths, but unit tests establish that known shapes are caught and say nothing about unknown ones.

So the honest characterization, now in the document: partial mitigation, unmeasured. A user should assume a determined, encoding-aware attacker defeats it.

What I'd add, because I think it matters more than the scanner's score: ioscan was never the boundary. What contains the consequence of a successful injection is the proxy's hard-deny of POST /pulls and PUT /pulls/{n}/merge for every ACMM mode — that holds whether or not the injection worked, because it is a rule table the model never sees and cannot argue with. The document now credits containment there and describes ioscan as doing three narrower things: raising the cost of casual injection, deterministically normalizing away invisible-character and homoglyph tricks, and producing an auditable record that something was withheld.

Red-team evaluation tracked in hivecommons/hive#6685 — corpus covering instruction override, Unicode steganography, homoglyphs, multi-layer encoding, smuggling via code blocks/diffs/labels/author fields, and split payloads across issue + comments; measuring what gets through and, for anything that does, whether the network deny rules still contain it. That last distinction is the finding that actually matters.

- **Token budget** — a seven-day rolling token budget that suppresses kicks
on exhaustion, limiting denial-of-wallet from a runaway or compromised
agent loop.
- **Log scrubbing** (`src/pkg/logscrub`, `security.md`) — redacts recognized

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this to prevent an model with access to these from leaking them? What's the purpose?

If so, what if I prompt inject something in which has you emit these in a base 64 encoded format or similar?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Two separate answers, and the second one is a bug report against ourselves — your hypothetical found a real defect.

Purpose. Log scrubbing is operational hygiene for logs Hive itself writes — so a token appearing in an error string doesn't end up in a pasted log snippet, a support bundle or a CI artifact. It is not an exfiltration control, it sits on Hive's logging path rather than the agent's egress path, and the document now says so rather than letting the word "redaction" imply containment. Your base64 case defeats it completely, as would hex, reversal, or splitting across tokens.

The bug. Running your question against the control that is supposed to catch exfiltration — ioscan canaries — exposed an asymmetry we had not noticed. The input path is encoding-aware: ScanInput normalizes Unicode steganography and confusables, and scanBase64Instructions decodes base64 blobs and rescans the plaintext. The egress path is not:

// src/pkg/ioscan/canary.go
for token, c := range m {
    if strings.Contains(text, token) {
        return CanaryLeak{Agent: c.Agent, Token: token, Source: source}, true
    }
}

A bare substring test. So an agent prompt-injected into emitting its HIVE-CANARY-… token base64-encoded passes the proxy check unflagged — exactly the attack canaries exist to detect. Filed as hivecommons/hive#6686; the fix reuses the decode-and-rescan machinery already present on the input path.

Severity is moderate rather than critical only because a canary hit was always positive evidence and a miss was never proof of absence, so it degrades to "no worse than not having it" rather than to false assurance. Two related limits are now also stated in the document: canaries are default-off, and git-receive-pack bodies are pack-encoded and opaque to the scan entirely, so pushes are covered only when fail_mode: closed refuses them outright.

Thanks for this one — it's the most useful comment in the review.

Comment on lines +504 to +505
has not pursued OpenSSF Best Practices Badge ("CII Best Practices")
certification at time of writing — not yet assessed whether this is planned.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You should do this. At least get a passing badge please. It's not that hard and the things you need to do for this are sane security things you either are or should be doing already.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed, and you're right that the criteria are things we should be doing anyway. The honest reason it hadn't been done is that nobody had considered it — "not yet assessed whether this is planned" was an evasion, not an answer.

It's planned now and tracked in hivecommons/hive#6684. Going through the passing criteria, most are already satisfied:

  • OSI-approved license (Apache-2.0)
  • Public version-controlled source with interim versions
  • Documented contribution process (CONTRIBUTING.md, GOVERNANCE.md)
  • Private vulnerability reporting via GitHub Security Advisories
  • Documented security response process (src/docs/security-response.md)
  • Automated test suite gating every PR
  • Static analysis in CI (golangci-lint, OpenSSF Scorecard weekly + on push)
  • Dependencies pinned by digest/SHA
  • No known unpatched vulnerabilities

So the outstanding work is registration and self-certification plus verifying the crypto and release-signing criteria — not engineering.

The document now says this rather than hedging, and will carry the badge ID and level once awarded instead of the issue number. I'd rather hand you a tracked commitment now than a claim.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Correction to my reply above — I gave you a worse answer than the facts warranted.

I said the badge was planned and that the document "will carry the badge ID and level once awarded." That was wrong. The badge was already awarded at the passing level on 2026-08-27, four days before your comment. I reported pending work on something that was already done.

Verified against the public API (https://www.bestpractices.dev/projects/14261.json):

field value
project 14261 (hive)
badge_level passing
badge_percentage_0 100
achieved_passing_at 2026-08-27T17:47:12Z
lost_passing_at null — never lost since

Badge: OpenSSF Best Practices — and it has been displayed at the top of README.md the whole time.

So your ask — "at least get a passing badge" — was already satisfied when you made it. Silver and gold sit at 15% and 22% and I am not claiming either.

Why the wrong answer happened, since the cause is the same defect you flagged elsewhere in this review. The security self-assessment said the project "has not pursued" the badge and had "not yet assessed whether this is planned." I answered you from the document rather than from the system, so a stale document propagated into a reviewer-facing claim. That is the identical failure mode as the bus-factor answer, where OWNERS had grown to three maintainers and the document still said one. Two instances in one review is a pattern, not a coincidence: this project's self-assessment was not being re-derived from observable state.

Fixed, and a genuine problem surfaced while fixing it. The badge entry had been filled in before Hive moved out of the KubeStellar org to the vendor-neutral hivecommons org. 67 of its criteria justifications — 70 URLs — still pointed at github.com/kubestellar/hive and hive.kubestellar.io. The justification text is the entire evidentiary value of a self-certified badge; if every link a reviewer follows to check a claim lands on the old home, the badge is decorative. All 70 now resolve to the current repository and site, homepage_url and repo_url are updated, the entry re-verifies at passing / 100%, and zero fields contain the old org.

The self-assessment is corrected in hivecommons/hive#6697, which records the badge ID, level and date instead of the issue number.

One item I am deliberately leaving open rather than quietly closing: the same document still describes Hive as part of the KubeStellar CNCF Sandbox project family, which is also stale post-migration. The correct wording depends on where the sandbox application lands (cncf/sandbox#516), so I would rather flag it to you than guess at it.

Comment on lines +466 to +468
assessment identifies, and is exactly the kind of gap CNCF Incubation
review should weigh: it is a bus-factor and response-capacity risk, not a
code defect.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yes, this is a huge risk. Why not mitigate it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It is a huge risk, and it turns out to be two risks that had been collapsed into one. One was already mitigated before you reviewed — our document was simply stale. The other is real, unmitigated, and now stated as the project's largest remaining process risk.

The bus-factor half was fixed three days before this review, and the document failed to say so. Timeline:

Date Event
2026-08-27 This PR opened; document says one approver
2026-08-28 OWNERS grows to three maintainers (hivecommons/hive#4996); security-response.md added (#5005)
2026-08-31 This review

The Maintainer Committee is now @clubanderson (IBM), @hanthor (Universal Blue) and @Danathar (independent) — three people across three affiliations, which improves vendor neutrality as well as bus factor — and security-response.md names the committee as the security response team with an explicit "whoever picks up the advisory drives it, and loops in the others for severity and disclosure timing" rule. That's on us for submitting a document that was obsolete on arrival, not on you for flagging what it said.

The enforcement half is not mitigated, and I don't want to bury it under the good news. I checked the live v4 branch protection API: required_pull_request_reviews is null — there are no required reviews at all, code-owner or otherwise. .github/CODEOWNERS covers the security-sensitive paths but is inert, and every entry still resolves to one owner. Concretely: a change to src/pkg/proxy/rules.go — the deny-rule table this document credits as the control an attacker cannot argue with — can merge on green CI with no human security reviewer.

Why it was left off: Hive is substantially self-hosting, and its own agent fleet merges green PRs, so blanket required review halts the repository. That's an explanation, not a justification, and I've written it that way in the document.

The resolution being pursued is scoped rather than all-or-nothing — enforce code-owner review only on src/pkg/proxy/**, the SUID contract script, Dockerfile*, .github/workflows/**, src/deploy/** and key/cookie handling, leaving ordinary paths on the automated merge path — plus expanding CODEOWNERS to all three maintainers so enforcement doesn't bottleneck on one person. Tracked in hivecommons/hive#6687.

Until that ships, the document tells evaluators to treat "the deny-rule table is enforced in code" as true and "the deny-rule table is protected from unreviewed change" as false.

Comment on lines +509 to +512
Not applicable — Hive does not currently publish named case studies of
production deployments in the Hive repository. (Not the same claim as "Hive has
no production users" — simply that no case-study document exists to cite
here.)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks like AI generated this for you. You should know the answer here and be able to write a correct response

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You were right, and it was worse than a style problem — the claim was factually false.

"Not applicable — Hive does not currently publish named case studies" is contradicted by ADOPTERS.md in the same repository, which lists seven adopters, three at Production maturity. In a document whose entire value is candor, that's the worst kind of error to ship.

Replaced with the real table. Two entries deserve a reviewer's attention specifically:

  • tunaos.org — a self-hosted hub coordinating two spokes at ACMM L5/L6 across 43 repositories. Those are the autonomy tiers where agents can merge, which makes it the strongest available evidence that the deny-rule and attribution controls hold outside the maintainers' own environment. Production evidence in hivecommons/hive#5773.
  • Project Bluefin — independently documented by the adopter at docs.projectbluefin.io/factory rather than merely self-reported by us.

Also KubeStellar Console (Production), plus Frostyard, Open Horizon, Open Horizon Services and Danathar at Pre-production.

What we genuinely don't have is a security case study — a narrative of an incident, an attempted injection, or an adopter's own security evaluation. That would be more useful to TAG-Security than an adopter table, and the document now says its absence is a real gap rather than pretending the question didn't apply.

Comment on lines +525 to +552
Items this assessment could not verify from the repository alone, listed
explicitly rather than guessed at:

- Whether issue #3760 (cited as the reason SBOM/provenance attestation is
disabled on container builds) documents a planned path to re-enabling SBOM
generation, or is a permanent decision.
- Whether any external channel (mailing list, Slack, Matrix) exists for
security-relevant project communication beyond GitHub issues/PRs and
private security advisories.
- Whether a formal, security-specific incident-response runbook (as distinct
from the disaster-recovery runbook at `docs/HUB_DISASTER_RECOVERY.md`)
exists outside the Hive repository (e.g., in an internal-only document) —
none was found in the public repository.
- Whether the project intends to pursue OpenSSF Best Practices Badge
certification.
- The current numeric OpenSSF Scorecard result — intentionally not quoted
here since it changes independently of this document; consult the live
badge/workflow output.
- Whether any prior informal security review (e.g., a maintainer's own
adversarial testing beyond what is documented in the ADRs and threat
model) occurred without producing a public artifact. This assessment
treats "no third-party audit" as accurate based on the absence of any
audit report in the repository, but cannot rule out unpublished internal
review.
- Whether `.github/CODEOWNERS` enforcement (branch-protection "Require
review from Code Owners") is planned to be enabled once the project has
more than one active human maintainer, or is intended to remain advisory
indefinitely regardless of maintainer count.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is the LLM telling you to clarify these (as I see it). Please go through and figure these out and update the document before we merge it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

will do - Thank you for the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Correct on both counts — those were prompts to the authors wearing a findings section's clothes, and a couple of them had answers sitting in the repository the whole time.

The section is gone. Every item was run down and answered in "Questions resolved since first review." Summary:

Question Answer
Does #3760 document a path to re-enabling SBOM? Closed, and it was never an SBOM decision. It's the bug report for /usr/local/bin/hive failing to exec with EPERM under containerd/k3s and rootless podman — attestations force an OCI image index, which permitted an overlayfs metacopy redirect presenting the binary as non-executable. Disabling them fixed a crash loop. Correct path is out-of-band SBOM generation (#6688), plus a CI assertion on manifest media type so the constraint stops being comment-only.
Any external communication channel? No, by design. CONTRIBUTING.md directs all discussion to GitHub issues/PRs "so decisions remain public and searchable." The Slack/Discord integrations are operator-alerting features of the software, not project channels — I'd conflated the two.
Security-specific incident-response runbook? Yes — and it didn't exist when the draft was written. security-response.md landed 2026-08-28. Still missing: post-incident review practice and user-notification SLA for a confirmed compromise.
Pursuing the OpenSSF badge? Yes, now tracked in #6684.
Current Scorecard score? Deliberately still not quoted — the workflow runs weekly and on every push to v4, and freezing a number makes the document wrong on a schedule. This is the one item I left unanswered on purpose, and the document says why.
Any informal security review or adversarial testing? No — and this is the most significant "no" in the list. No red team, no adversarial evaluation, no measured detection rate for ioscan anywhere in the repository. The hardening work cited by issue number throughout the threat model is maintainer-identified and maintainer-fixed, which is not adversarial review. Tracked in #6685.
Will CODEOWNERS enforcement be enabled? Live v4 branch protection currently requires no PR reviews at all. Scoped enforcement over security-critical paths tracked in #6687.

Two of those answers are "no," which is the point — they're findings, and they read better as findings than as questions.

@clubanderson
clubanderson requested a review from a team as a code owner September 9, 2026 15:56
@clubanderson clubanderson changed the title 📖 Add KubeStellar Hive security self-assessment 📖 Add Hive (Hive Commons) security self-assessment Sep 9, 2026
@JustinCappos

Copy link
Copy Markdown
Contributor

@clubanderson and others from the team. please let me know when you've addressed the prior comments and we can provide another review.

clubanderson pushed a commit to clubanderson/toc that referenced this pull request Sep 11, 2026
Revision 2 in response to the seven review comments on cncf#2286.

- Add a deployer-facing threat section: the attack path from a stranger's
  issue to a repository write, what stands in the way of each concern and
  what does not, the three settings that determine exposure, and the worst
  realistic outcome at L6.
- State plainly that ioscan has never been red-teamed and has no measured
  detection rate; recharacterize it as a partial mitigation and credit
  containment to the network deny rules instead.
- Re-scope log scrubbing as log hygiene rather than an exfiltration control,
  and answer the base64 question directly. Running that question against the
  canary path surfaced a real defect: the egress check is substring-only, so
  an encoded canary is not detected, while the input path does decode base64.
- Replace the false "Case studies: not applicable" claim with the seven
  adopters in ADOPTERS.md, three in production, one at ACMM L5/L6 across 43
  repositories.
- Record that the maintainer roster grew from one to three across three
  affiliations and that a security response process now exists; restate the
  remaining risk as unenforced code ownership rather than bus factor.
- Correct the SBOM explanation: #3760 is a container-runtime exec defect
  caused by attestations forcing an OCI index, not an SBOM deprioritization.
- Delete the open-questions section and answer every item against the
  repository, including two answers that are "no".
- Commit to the OpenSSF Best Practices badge.

Tracking issues filed: hivecommons/hive#6684, #6685, #6686, #6687, #6688.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Andy Anderson <clubanderson@gmail.com>
@clubanderson

Copy link
Copy Markdown
Contributor Author

@JustinCappos — all seven comments are addressed and replied to individually. Ready for another review.

Short version of what changed, and one thing you should see first.

Your base64 question found a real bug. You asked, about secret redaction, "what if I prompt inject something in which has you emit these in a base 64 encoded format or similar?" Running that against the control that's actually supposed to catch exfiltration — ioscan canaries — exposed an asymmetry nobody had noticed. The input path decodes base64 and normalizes Unicode before scanning. The egress path is a bare strings.Contains. So an agent induced to emit its canary base64-encoded passes the proxy check unflagged, which is precisely the attack canaries exist to detect. Filed as hivecommons/hive#6686. That's a defect located from a one-sentence hypothetical against a codebase you don't know, and it's the most useful comment in the review.

The rest:

Your comment What changed
"If I deploy this, what should I worry about?" New section stating the attack path plainly — including that no step necessarily has a human read the attacker's text first — with a concern → what-stops-it → what-does-not table, the three settings that determine exposure, and the worst realistic outcome at L6.
"Have you red teamed?" No. Nothing in the repo establishes a detection rate. ioscan is now described as an unmeasured partial mitigation, with containment credited to the proxy hard-deny of POST /pulls and PUT /pulls/{n}/merge — which holds whether or not injection succeeds. Evaluation tracked in #6685.
"Is this to prevent a model leaking them?" Log scrubbing re-scoped as hygiene for Hive's own logs and explicitly not an exfiltration control. Plus the bug above.
"At least get a passing badge." Agreed — #6684. Most passing criteria already met; outstanding work is registration, not engineering.
"This is a huge risk. Why not mitigate it?" Half of it was mitigated three days before your review and our document was stale: OWNERS went from one maintainer to three across three affiliations on 2026-08-28, with security-response.md landing the same day. The other half is real and unmitigated — live v4 branch protection requires no PR reviews at all, so a change to the proxy deny-rule table can merge on green CI with no human security reviewer. Scoped enforcement tracked in #6687.
"Looks like AI generated this." It had, and the claim was false: "Case studies: not applicable" is contradicted by ADOPTERS.md — seven adopters, three in production, one running ACMM L5/L6 across 43 repositories. Replaced with the real table.
"Go through the open questions." Section deleted; every item answered against the repository. Two answers are "no" — no external comms channel (by design), and no adversarial testing has ever been performed.

Five issues filed: #6684 badge · #6685 red team · #6686 canary bug · #6687 code-owner enforcement · #6688 out-of-band SBOM.

The canonical copy is updated in lockstep at hivecommons/hive#6692. All 37 external links verified 200, all internal anchors resolve.

The document is meaningfully less confident than it was, which I think is the correct direction. Thanks for the review — it was substantive, and it improved the software and not just the document.

clubanderson added a commit to hivecommons/hive that referenced this pull request Sep 11, 2026
* Revise security self-assessment after TAG-Security review

Mirrors the revision made for cncf/toc#2286 into the canonical copy.

Seven review comments asked for the parts the first draft hedged on:

- New deployer-facing section: the path from a stranger's issue to a
  repository write, what stands in the way of each concern and what does
  not, the three settings that determine exposure, and the worst realistic
  outcome at L6.
- ioscan efficacy is now stated as unmeasured. No red-team exercise exists,
  so it is described as a partial mitigation, with containment credited to
  the proxy deny rules rather than to the scanner.
- Log scrubbing re-scoped as hygiene for Hive's own logs, explicitly not an
  exfiltration control. Answering the reviewer's base64 question against the
  canary path surfaced a real defect, filed as #6686: the egress check is a
  substring test while the input path decodes base64.
- "Case studies: not applicable" was wrong. ADOPTERS.md lists seven
  adopters, three in production, one at ACMM L5/L6 across 43 repositories.
- Records the roster growth from one maintainer to three across three
  affiliations, and security-response.md; restates the remaining risk as
  unenforced code ownership rather than bus factor.
- Corrects the SBOM explanation: #3760 is a container-runtime exec defect
  caused by attestations forcing an OCI index, not an SBOM decision.
- Open-questions section replaced with answers, including two that are "no".

Tracking issues: #6684, #6685, #6686, #6687, #6688.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Andy Anderson <clubanderson@gmail.com>

* Fix changelog fragment: must start with a '- ' entry bullet

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Andy Anderson <clubanderson@gmail.com>

---------

Signed-off-by: Andy Anderson <clubanderson@gmail.com>
Co-authored-by: Andy Anderson <clubanderson@gmail.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@clubanderson

Copy link
Copy Markdown
Contributor Author

Pushed a correction to this self-assessment: the OpenSSF Best Practices badge status was wrong in this document, in the direction of understating the project.

The document said the badge was "not yet held, and now being pursued." It has in fact been held at the passing level since 2026-08-27 — project 14261, 100% of the passing criteria, never lapsed — which is four days before the review that asked for it. README.md in the project repository had been displaying the badge the whole time.

@JustinCappos — this is the correction to my reply on your badge comment; you asked for a passing badge and we already had one, so answering "we'll get it" was wrong. Separately corrected there.

What was actually outstanding turned out to be the badge entry, not the badge. Hive moved out of the kubestellar org to the vendor-neutral hivecommons org on 2026-09-03, and 67 of the entry's criteria justifications — 70 URLs — still pointed at github.com/kubestellar/hive and hive.kubestellar.io. For a self-certified badge the per-criterion justifications are the entire evidentiary value, so every link a reviewer might follow to check a claim landed on the old home. All 70 now resolve to the current repository and site; the entry re-verifies at passing / 100% with zero fields referencing the old org.

Root cause, since it is the same defect you flagged elsewhere in this review. This document was being written from itself rather than re-derived from observable state. That is what produced the stale maintainer count, and it produced this too — the CNCF Sandbox application (cncf/sandbox#516) has stated "OpenSSF Best Practices passing badge at 100%" correctly since filing, so the accurate fact was already written down in a sibling document. The badge status in this file is now taken from the programme's public API, and the section records both prior errors rather than quietly overwriting them.

Consistency pass. Correcting the one section left three other passages contradicting it, including the response table at the top of this document. Those are fixed in hivecommons/hive#6715, and this file has been regenerated from the canonical copy so the two agree exactly.

Affiliation is consistent across all three places: Hive is an independent project in the hivecommons org, transferred out of the KubeStellar org on 2026-09-03, with KubeStellar recorded as its incubating parent and first production adopter.

clubanderson pushed a commit to hivecommons/hive that referenced this pull request Sep 11, 2026
CNCF TAG-Security asked, on the ioscan section of the self-assessment
(cncf/toc#2286): "have you red teamed to know how well this works? Is
this perfect defense, works decently well, a partial mitigation, etc.?"

The honest answer was no. pkg/ioscan has 56 unit tests, but they assert
that KNOWN shapes are caught — by construction every one passes, and they
cannot produce a detection rate. The self-assessment recorded efficacy as
"unmeasured" and committed to measuring it.

Measured now, against 43 adversarial payloads through the production
entry point (ioscan.ScanInput, shipped defaults):

  blocked (withheld from the agent)     16   37%
  detected but injected anyway           8   19%
  missed entirely                       19   44%
  ------------------------------------------------
  reached the agent verbatim            27   63%

Per family, detection is very uneven: unicode 6/8 blocked, smuggling 4/7,
direct-override 2/10, split-payload 0/4. Plus 2 of 6 benign controls were
redacted — including an issue *reporting* a prompt-injection bug.

Three findings worth more than the headline number:

  - The middle bucket is not obvious. blockedInput requires Critical, or
    Injection at High+, so every Medium finding is recorded for audit and
    the raw text is still injected. "ioscan findings" is not "attacks
    stopped", and nothing said so before.
  - Split payloads score 0% structurally, not for want of a rule. Each
    untrusted segment is scanned separately, so a phrase split across two
    comments is never a single string to match.
  - Two single characters (U+FF29 fullwidth I, U+00AD soft hyphen) defeat
    the STRONGEST family outright. Filed as #6708; the U08 fix is verified
    to be a one-character addition to isInvisibleControl.

Item 3 of the issue — does the network layer still contain what gets
through — is measured rather than claimed:
TestRedTeamContainmentHoldsForEveryMode checks POST /pulls and
PUT /pulls/{n}/merge against all four ACMM modes on every run. That is
the real finding: the injection defense of record is the proxy deny rule,
not the text scanner.

The corpus is a RATCHET, not a snapshot. TestRedTeamCorpusOutcomesAreStable
pins each observed outcome and fails in BOTH directions — a regression,
and equally an improvement, because an improvement makes the published
miss rate wrong. Both branches were verified by deliberately breaking and
deliberately improving a rule; the improvement path prints "re-run the
report and republish". So the document's numbers cannot silently drift
from the code.

Not measured, and said so in the document rather than estimated: the
optional LLM classifier (default-off, and fails open, so the
deterministic floor here is the worst case either way), the output path,
and the canary egress path (#6686).

Fixes #6685

Signed-off-by: Danathar <Danathar@users.noreply.github.com>
@clubanderson

Copy link
Copy Markdown
Contributor Author

Follow-up correction to my earlier note in this thread — two numbers I cited there have since been superseded, and the mirror is now re-synced from canonical (7ea636a).

1. The red-team figures moved, because the gaps they measured were closed.

My earlier comment quoted the evaluation's original result. Since then hivecommons/hive#6714 closed the two Unicode gaps that evaluation found — U+FF29 FULLWIDTH LATIN CAPITAL I was missing from the confusable fold table, and U+00AD SOFT HYPHEN was not treated as an invisible control. Each defeated the entire Unicode family with a single character.

metric as first published now
attack payloads withheld from the agent 16 (37%) 18 (42%)
reached the agent verbatim 27 (63%) 25 (58%)
missed (no rule fired) 19 (44%) 17 (40%)
detected but not blocked 8 (19%) 8 (19%)
Unicode steganography family 6/8 blocked 8/8

The overall characterisation is unchanged and deliberately so: this is still a partial mitigation, and a narrow one. 58% of attack payloads still reach the agent verbatim, paraphrased instruction override is still 2/10, and split payloads are still 0/4 — structurally, because each untrusted segment is scanned separately. What bounds the consequence of a successful injection is not ioscan; it is the proxy's hard deny of POST /pulls and PUT /pulls/{n}/merge.

2. The canary egress weakness recorded here has been fixed.

Revision 2 recorded, as a known weakness, that the canary egress check matched literal substrings only — so a base64-encoded canary passed unflagged (#6686). That is now closed: CanaryRegistry.Scan decodes and normalizes before matching. The residual limitation is stated rather than dropped — the transform list is finite and enumerated, so an encoding it does not model still passes, and git-receive-pack bodies remain opaque.

3. Why there were stale numbers to correct at all.

Worth stating plainly, since this is the same class of problem @JustinCappos flagged originally with the stale maintainer count. The regression test that publishes these outcomes fails when a result changes in either direction, including improvements, precisely so an improvement cannot silently invalidate a published figure. It worked — but the republish updated the results table and the machine-readable outcomes while leaving the prose that summarised them untouched, so the document contradicted itself within three lines of the corrected table, and the stale figures had already been copied into this mirror.

Fixed in hivecommons/hive#6741 as a repository-wide sweep for the figures rather than an edit to the one file that happened to be noticed, and verified with grep across every .md and .go file rather than by re-reading the document. That is now three instances of the same failure mode in this document set (maintainer count, OpenSSF badge status, red-team figures), which I think is the more useful thing to report than any individual number.

This mirror is generated from the canonical copy at src/docs/security-self-assessment.md and verified byte-identical; corrections continue to flow repo → here, never the reverse. This push also fixes two relative releases.md links that resolved nowhere in this repository.

@sherine-k sherine-k left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

hi @clubanderson
Thank you for this PR and for the recent updates you have made.
I have added a few questions, especially to make sure that all angles of the threat model were equally addressed.
A comment on the style rather than the content: Some sections looked somewhat dense and harder to read for me. Do you think you could do something?

Comment on lines +111 to +113
- **Hub operators / SaaS platform operators** — run the central hub that
coordinates registered spokes and, for hosted spokes, provisions
infrastructure and injects GitHub App credentials.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I was wondering if any threats were identified on this interface between hub and spokes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes — and the document under-describes this interface. What is in place, and what is not:

Spoke → hub. Each spoke authenticates its heartbeat with a per-hive derived bearer (keyderive.PerHiveKey, bound to trust domain + hive ID), verified across live key generations so the master can rotate without a flag day (pkg/hub/hub_keys.go). The fleet-wide legacy bearer lane has been deleted. Heartbeats carry operational telemetry only — never credentials.

Hub → spoke. The hub returns config — and, for hosted spokes, GitHub App credentials — in the heartbeat response. Today the spoke trusts TLS to its configured hub URL for that; the response body is not independently signed or bound to a hive ID / sequence. The tokens the hub mints (SSO, delegation) are asymmetric — Ed25519, spokes hold only the public key — but that does not extend to the config push.

Threats we will list in the document

Threat Status
Hub compromise → fleet-wide credential/config push Real; the hub is the highest-value asset. Mitigated only by hub hardening and the fact that a spoke gets its own App key alone.
Spoke impersonation to the hub Bounded: per-hive keys mean one leaked bearer affects one hive; the hub cannot be made to accept another hive's identity with it.
Replay / rollback / mis-delivery of a pushed config Open. Filed as hivecommons/hive#7082: sign the heartbeat response with the hub's existing Ed25519 key and bind it to hive_id + a monotonic sequence.

Comment on lines +108 to +110
- **Contributors via ClankeR relay** — external contributors who donate
compute by running an agent against a hive's queue over a relay protocol
(see [contributor-relay.md](https://github.com/hivecommons/hive/blob/v4/src/docs/contributor-relay.md)).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

How does hive authenticate a contributor/relay?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The relay is authenticated with a per-contributor registration token; the doc should say how, so here it is and it will be added.

  • A contributor registers once against a GitHub identity. The hive issues a registration token and stores only its SHA-256 hash in the contributor profile (profiles are 0600 — they also carry PII). The plaintext is shown once and lives in the contributor's ~/.config/hive/contributor.env.
  • The relay presents the token as a bearer when it connects to wss://<hive>/api/contribute/ws; the hive hashes and compares in constant time (contributorProfileFromRegistrationToken).
  • Every contributor has a trust tier with per-tier rate limits and model admission (contributor-trust-and-roles.md).
  • Per task the hub mints a 55-minute scoped GitHub token for the PR push and re-mints it while the task is live; a task is abandoned after 30 minutes without forward progress or a 4-hour absolute backstop, and the lease is fenced to that contributor identity.

Weaknesses, stated: the registration token is a long-lived bearer with no expiry or rotation today, and the contributor's machine is outside our trust boundary — a malicious relay can submit a malicious PR. That is why relay output enters at exactly the same PR-review gate as any external contributor's PR and is never merge-eligible on its own.

Comment on lines +100 to +102
- **Repository maintainer / hive operator** — configures ACMM autonomy level,
which repositories are governed, which agents run, and credential sources
(GitHub App vs. PAT, inference backend keys).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is also an area that could be worth expanding on for the self assessment:
where are GH tokens and inference keys stored? What can be compromised if hive operator is compromised? What are threats that hive operators face while installing / updating ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good question — the document names the credentials but never says where they live or what falls if each actor falls. Answering in three parts; the document will be updated to carry this.

Where they are stored

Credential Hosted spoke Hub Self-hosted
GitHub App private key Projected from the hive-secrets Kubernetes Secret, whole-volume mount at 0440 with pod fsGroup. A spoke receives only its own App key — the earlier fleet-wide key broadcast was removed (audit finding N1, CWE-200). One 0600 file per cluster, keyed by cluster ID; never rendered into any spoke manifest. hive.yaml github.key_file or env; operator-managed.
Inference keys Same hive-secrets Secret; dashboard-entered keys are written by a scoped hive-secrets-writer role. n/a hive.yaml / env.
Hub master (HIVE_HUB_SECRET) Never leaves the hub. Spokes get derived sub-keys (pkg/keyderive, domain-separated per hive): a heartbeat bearer, a session-verify key, and the SSO public key. Hub only. n/a

Blast radius

  • Spoke operator compromised → that spoke's App installation scope (the repositories it governs), its dashboard token, its inference keys. Not other tenants' keys, not the hub master, and cannot mint an admin session or an SSO-as-any-owner token (those keys are hub-only).
  • Hub compromised → the worst case in the system: the hub provisions spokes and pushes config and App credentials to them on the heartbeat lane, so a hub compromise is fleet-wide. That asymmetry belongs in the threat model explicitly and will be added (see the reply on L113).

Install / update threats

  • Base images pinned by digest (FROM golang@sha256:…, node@sha256:…); CLI backends pinned by version, and by per-arch SHA-256 where upstream publishes one (Goose, muse — mismatch hard-fails the build); npm installs run with --ignore-scripts.
  • Upgrades are pull-based: the spoke patches its own Deployment through a namespaced ServiceAccount whose role is get/patch on that one Deployment; the hub does not need cluster credentials to a spoke's cluster to upgrade it.
  • Gap, already in the document and repeated here: SBOM/provenance attestations are disabled on image builds (#3760), so the supply chain is pinned but not attestable end-to-end.

Comment on lines +289 to +291
**How well does it work? Measured: a partial mitigation, and a narrow one**
— see [ioscan-red-team.md](https://github.com/hivecommons/hive/blob/v4/src/docs/ioscan-red-team.md) for the corpus, the
methodology and the full per-case table. Against 43 adversarial payloads

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I probably haven't thoroughly read ioscan-red-team.md, but this section feels like duplicating the content of that doc?
If so, it might be nicer to just summarize the gist here, and let the reader review more by following the link

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed — it grew in place while answering the first round and now duplicates the source. I will cut it to the gist (corpus size and families; 42% withheld / 58% reached the agent; Unicode 8/8 vs the weak families named; containment credited to the proxy) and point at ioscan-red-team.md for methodology and the per-case table.

|---|---|---|
| Attacker text talks an agent into **merging** malicious code | `PUT /pulls/{n}/merge` is **hard-denied for every ACMM mode** at the proxy (`rules.go:215-226`), as is the GraphQL merge mutation. The agent never holds a credential path to merge; `hive-merge` runs as Hive with a SHA-pinned eligibility binding. | `ioscan`. A sufficiently clever prompt defeats a scanner; it does not defeat a rule table the model never sees. |
| Attacker text talks an agent into **opening a PR** with a backdoor | `POST /pulls` is likewise hard-denied for every mode. PR creation goes through `hive-open-pr` as the App bot, attribution-stamped and audit-logged. | Nothing prevents the *content* of a legitimately-created PR from being attacker-influenced. Review it like any other PR. |
| Attacker exfiltrates your **secrets** via the agent | Mode-tiered tokens mean low-tier agents never hold code-write credentials. Optional `ioscan` canaries detect prompt contents echoed into outbound GitHub writes at the proxy, including base64/hex/URL-encoded, reversed and split spellings of the token. | Canaries are **default-off**, and their transform list is finite — an encoding it does not model still passes. `git-receive-pack` bodies are unscannable, and non-GitHub egress is tunneled without inspection. |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What would the impact of setting fail_mode: closed and ioscan.canaries to enabled by default be?
Are these disabled by default because they are only partial mitigations?
Are there plans to improve the security provided with these 2 modes?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fair questions, and one correction to how the document frames the first mode.

fail_mode: open does not pass the injection through. On a Critical finding, open redacts the offending text and continues the kick; closed blocks the kick and records an ioscan_fail_closed audit entry. The tradeoff is availability: closed-by-default turns every Critical false positive into a stalled queue item that needs an operator, and on public repositories Critical fires rarely but not never. The document's current wording ("continue to process untrusted input") overstates the exposure and will be corrected.

Canaries are off by default for a plainer reason: the egress half was not yet trustworthy enough to make everyone pay for it. Justin's base64 question in the first review round exposed exactly that — the input path decodes base64 and normalizes Unicode, the egress check was a bare strings.Contains (hivecommons/hive#6686). Shipping that on by default would have given a false sense of coverage.

Yes, they are partial mitigations, and the document is explicit that containment is the proxy's hard-deny rules, not ioscan (ioscan-red-team.md: 58% of adversarial payloads reach the agent; the network boundary is what held in every run).

Plan, now tracked as hivecommons/hive#7083:

  1. After #6686 lands, ioscan.canaries defaults on (explicit false still opts out).
  2. fail_mode: closed becomes the default at ACMM ≥ L5 — the levels where agents can merge — while L1–L4 stay open. Per-hive override remains.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Correction to my reply above, caught while folding it into the document: #6686 is already fixed — closed 2026-09-11 by hivecommons/hive#6701 and #6720, before this review round. The canary egress check now normalizes and decodes outbound bodies (base64 incl. nested/URL-safe, hex, percent-encoding, reversal, homoglyph/zero-width, separator splitting) before matching. The document already said so; I answered from memory of the first round. Apologies.

So the "after #6686" precondition is gone, and the plan in hivecommons/hive#7083 simplifies to: flip ioscan.canaries on by default now, and make fail_mode: closed the default at ACMM ≥ L5. Issue updated accordingly. The tradeoff statement stands: both are partial mitigations, the transform list is finite and enumerated, and containment remains the proxy deny rules.

Comment on lines +488 to +497
- **Container build**: multi-stage `src/Dockerfile`, digest-pinned base
images (`golang:1.27-alpine@sha256:...`, `node:26-slim@sha256:...` ×2), a
build-time SUID-contract check that fails the build on any unexpected
setuid/setgid binary, and checksum-verified tool downloads (tmux, ttyd,
`gh`, goose, `su-exec`).
- **No SBOM/provenance attestation** is attached to built images — explicitly
disabled (`sbom: false`, citing issue #3760) rather than merely absent by
omission. Release SBOMs are generated out-of-band as standalone SPDX JSON
files and attached to the GitHub Release, so the published image manifests
stay plain.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The container image is definitely more secure with the above in place.
The footprint of the image is rather big, with codex, claude, agy, goose, copilot, node all bundled together.
Is that by design ? ie all in one pod?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

By design for the resident spoke, yes — and the cost is real, so both belong in the document.

Why one image. A spoke runs several agent roles in one pod, one tmux session per agent, and the operator picks the backend per role (e.g. Copilot for the scanner, Claude for quality, Codex for review). Bundling every supported CLI lets that be a config change rather than an image rebuild, and lets a hosted spoke switch backends when a provider is degraded.

What it costs. A large surface in one image, and no per-agent kernel boundary — agents are separated by UID, not by container (already listed as weakness #2, hivecommons/hive#2804). Mitigations in place: non-root UIDs, npm --ignore-scripts, version/digest pinning with per-arch SHA-256 where upstream publishes it, and the MITM proxy as the egress boundary regardless of which CLI is talking.

Direction. The contributor path already ships a separate, slimmer Dockerfile.contributor. For the resident spoke the roadmap is per-backend image variants so an operator running only Copilot does not carry Codex/Claude/Goose; the ACMM pack already knows which backends a level uses, so the selection can be automatic. I will add this as a stated tradeoff in the components section rather than leaving the reader to infer it from the Dockerfile.

@clubanderson

Copy link
Copy Markdown
Contributor Author

@sherine-k — thank you for the second pass. All six inline questions are answered individually; two of them surfaced things worth tracking rather than just describing, so they now have issues:

On readability: agreed. The next push to this PR will (a) fold the answers above into the document — a credential-storage/blast-radius table, a hub↔spoke threat table, the relay authentication paragraph, the image tradeoff — (b) collapse the red-team section to a summary that links to ioscan-red-team.md, and (c) break the dense sections (Actions, Critical components, Weaknesses) into short paragraphs and tables, moving detail to the appendix. I will ping when it is up.

@clubanderson

Copy link
Copy Markdown
Contributor Author

@sherine-k — the revision is up (6eaa9cb), mirrored from the canonical copy (hivecommons/hive#7088). What changed, mapped to your comments:

Your comment Where it landed
L102 — token/key storage, blast radius, install/update New Credentials: where they live and what falls with them — storage table (spoke / hub / self-hosted), blast-radius table per compromised actor, install/upgrade facts and the attestation gap
L110 — relay authentication New Contributor relay authentication
L113 — hub↔spoke threats New Hub ↔ spoke interface — both directions + hub-minted tokens, and a threat table. Unsigned config push filed as hivecommons/hive#7082
L196 — fail_mode / canaries Framing corrected in three places (open redacts and continues); plan in hivecommons/hive#7083 — canaries on everywhere, closed at ACMM ≥ L5
L291 — red-team duplication Cut to a five-row summary table linking to ioscan-red-team.md
L497 — one image, every backend Stated as a tradeoff with cost, mitigations and roadmap under Security relevant components
Style Densest paragraphs replaced with tables; a stale appendix answer ("no adversarial testing has occurred") corrected to say what was done and what still has not been (external review)

A Revision 3 table at the top maps each point to its change, same as Revision 2 did for Justin's round. Ready for another look.

clubanderson added a commit to hivecommons/hive that referenced this pull request Sep 15, 2026
cncf/toc#2286 second review (sherine-k) asked six questions the document
had answered by control rather than by consequence. Changes:

- New "Credentials: where they live and what falls with them": storage
  table per credential per deployment shape, blast-radius table per
  compromised actor (spoke / relay / hub), install and upgrade facts.
- New "Hub <-> spoke interface": both directions and the hub-minted
  tokens, plus a threat table. Finding: hub->spoke heartbeat responses
  carrying config and App credentials rely on TLS alone and are not
  signed or hive-bound. Filed as #7082.
- New "Contributor relay authentication": hashed registration token,
  constant-time compare, trust tiers, 55-minute per-task token, and the
  stated weaknesses.
- Corrected the document's own framing of fail_mode: open in three
  places -- it REDACTS a Critical finding and continues, it does not pass
  the injection through. Defaults plan (canaries on everywhere, closed at
  L5+) tracked in #7083.
- Red-team paragraph in critical component 2 replaced with a summary
  table linking to ioscan-red-team.md.
- "One image, every backend" stated as a tradeoff with cost, mitigations
  and roadmap.
- Appendix answer "no adversarial testing has occurred" was stale since
  #6685 closed; now says what was done and what still has not been
  (external review).
- Revision 3 table at the top mapping each review point to its change.

Link checker: all relative links and anchors resolve.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Andy Anderson <andy@clubanderson.com>
Adds the CNCF TAG-Security self-assessment for KubeStellar Hive under
projects/kubestellar/sub-projects/hive/security-assessment/, alongside the
existing Console assessment.

Hive orchestrates fleets of AI coding agents that autonomously maintain
software projects — filing issues, opening pull requests, reviewing, and at
high autonomy levels merging. The security model is therefore unusual for a
CNCF project: it runs model output with credentials that can write to source
repositories, so the assessment focuses on the boundaries that contain that.

Covered: the ACMM autonomy levels and agent mode ladder that gate write and
merge capability, ioscan prompt-injection scanning of untrusted input, the MITM
proxy enforcing network egress rules, GitHub App credential handling, the
append-only audit log that provides attribution, and the private vulnerability
reporting process.

The assessment states known weaknesses rather than omitting them, including
that ioscan's default fail mode is open, that agents share a container rather
than being sandboxed per run, and that the project currently has a single
maintainer. It also records that no third-party security audit or penetration
test has been performed.

Source document:
https://github.com/kubestellar/hive/blob/v4/src/docs/security-self-assessment.md

Relative links from the source have been rewritten to absolute Hive URLs so
they resolve from this repository.

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
clubanderson and others added 6 commits September 15, 2026 14:38
Hive was transferred out of the KubeStellar org to its own vendor-neutral
org, hivecommons, on 2026-09-03, and is applying to Sandbox as an
independent project (cncf/sandbox#516). It is no longer a KubeStellar
subproject, so the assessment moves from
projects/kubestellar/sub-projects/hive/ to projects/hive-commons/.

- Rewrite all kubestellar/hive source links to hivecommons/hive
- Reframe scope, ecosystem, and related-projects sections: KubeStellar is
  now described as Hive's origin and first production adopter rather than
  its host org
- Correct "Incubation application" to Sandbox application (cncf#516)
- Point the sibling Console assessment link at an absolute URL, since the
  previous relative path no longer resolves from the new location
- Fix a pre-existing broken link: docs/landscape.md to src/docs/landscape.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Andrew Anderson <andy@clubanderson.com>
Revision 2 in response to the seven review comments on cncf#2286.

- Add a deployer-facing threat section: the attack path from a stranger's
  issue to a repository write, what stands in the way of each concern and
  what does not, the three settings that determine exposure, and the worst
  realistic outcome at L6.
- State plainly that ioscan has never been red-teamed and has no measured
  detection rate; recharacterize it as a partial mitigation and credit
  containment to the network deny rules instead.
- Re-scope log scrubbing as log hygiene rather than an exfiltration control,
  and answer the base64 question directly. Running that question against the
  canary path surfaced a real defect: the egress check is substring-only, so
  an encoded canary is not detected, while the input path does decode base64.
- Replace the false "Case studies: not applicable" claim with the seven
  adopters in ADOPTERS.md, three in production, one at ACMM L5/L6 across 43
  repositories.
- Record that the maintainer roster grew from one to three across three
  affiliations and that a security response process now exists; restate the
  remaining risk as unenforced code ownership rather than bus factor.
- Correct the SBOM explanation: #3760 is a container-runtime exec defect
  caused by attestations forcing an OCI index, not an SBOM deprioritization.
- Delete the open-questions section and answer every item against the
  repository, including two answers that are "no".
- Commit to the OpenSSF Best Practices badge.

Tracking issues filed: hivecommons/hive#6684, #6685, #6686, #6687, #6688.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Andrew Anderson <andy@clubanderson.com>
Mirrors the canonical document from hivecommons/hive after correcting the
badge status. The mirror said the badge was not yet held and being pursued;
it was in fact awarded at the passing level on 2026-08-27, project 14261,
four days before the TAG-Security review that asked for it.

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
Re-mirrors the canonical document after the badge-consistency pass in
hivecommons/hive. Brings across the measured ioscan red-team results and
makes all badge statements agree: project 14261, passing, 100%, awarded
2026-08-27.

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
Canonical hivecommons/hive v4 now reports 42% of attack payloads withheld
and 58% reaching the agent (Unicode family 8/8) after #6714 closed the
fullwidth-capital and soft-hyphen gaps, and records the canary egress
check as encoding-aware after #6686 was fixed. This mirror still carried
the superseded 37%/63%, 6/8 and substring-only text.

Also rewrites two relative releases.md links that resolve nowhere in this
repository.

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
…urity review)

Mirrors hivecommons/hive#7088. Adds credential storage and blast-radius tables, hub<->spoke interface threat table (#7082), contributor relay authentication, corrected fail_mode framing (#7083), condensed red-team summary, image-composition tradeoff, and a readability pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Andrew Anderson <andy@clubanderson.com>
@clubanderson
clubanderson force-pushed the docs/hive-security-self-assessment branch from 6eaa9cb to 9c772ad Compare September 15, 2026 18:39
clubanderson added a commit to hivecommons/hive that referenced this pull request Sep 15, 2026
Two ioscan hardening modes shipped off-by-default; the CNCF TAG-Security
self-assessment (cncf/toc#2286) asked why and whether that would change.

- ioscan.canaries now defaults ON. It was off because the egress check was
  not encoding-aware (#6686); that reason has expired — CanaryRegistry.Scan
  now normalizes and decodes outbound bodies before matching (#6701, #6720).
  Mirror the ioscan.enabled *bool pattern: Canaries is now *bool, nil == on,
  explicit false still opts out (CanariesEnabled()).

- ioscan.fail_mode now defaults to closed at ACMM L5/L6 (the levels where
  agents can merge), via the L5/L6 packs' governor.ioscan_fail_mode, staying
  open at L1-L4. Expressed through the same pack-governor mechanism as
  plan_auto_approve (IoscanFailModeForLevel), resolved by FailClosedAtLevel;
  an explicit operator fail_mode overrides the level default in either
  direction at every level.

Docs (security-self-assessment.md, acmm-policy-matrix.md, ioscan.md,
hive.yaml.example) document both defaults with the tradeoff stated plainly:
closed-by-default turns every Critical false-positive at L5/L6 into a stalled
queue item that an operator must clear by hand.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Andrew Anderson <andy@clubanderson.com>
clubanderson added a commit to hivecommons/hive that referenced this pull request Sep 15, 2026
Two ioscan hardening modes shipped off-by-default; the CNCF TAG-Security
self-assessment (cncf/toc#2286) asked why and whether that would change.

- ioscan.canaries now defaults ON. It was off because the egress check was
  not encoding-aware (#6686); that reason has expired — CanaryRegistry.Scan
  now normalizes and decodes outbound bodies before matching (#6701, #6720).
  Mirror the ioscan.enabled *bool pattern: Canaries is now *bool, nil == on,
  explicit false still opts out (CanariesEnabled()).

- ioscan.fail_mode now defaults to closed at ACMM L5/L6 (the levels where
  agents can merge), via the L5/L6 packs' governor.ioscan_fail_mode, staying
  open at L1-L4. Expressed through the same pack-governor mechanism as
  plan_auto_approve (IoscanFailModeForLevel), resolved by FailClosedAtLevel;
  an explicit operator fail_mode overrides the level default in either
  direction at every level.

Docs (security-self-assessment.md, acmm-policy-matrix.md, ioscan.md,
hive.yaml.example) document both defaults with the tradeoff stated plainly:
closed-by-default turns every Critical false-positive at L5/L6 into a stalled
queue item that an operator must clear by hand.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Andrew Anderson <andy@clubanderson.com>
clubanderson added a commit to hivecommons/hive that referenced this pull request Sep 15, 2026
The hub→spoke heartbeat response — which carries config and, for hosted
spokes, GitHub App credentials — was authenticated by the TLS channel
alone. A TLS-terminating middlebox or a misconfigured HIVE_HUB_URL could
push arbitrary config/credentials to a spoke, a response captured for
hive A could be replayed to hive B, and an old response could be replayed
to roll config back (cncf/toc#2286, TAG-Security second review).

Sign the response body with the hub's EXISTING Ed25519 key already derived
from the master seed (infoSSOEd25519Seed) — the same key used for SSO and
session tokens. No new key, no new distribution, no new primitive. Spokes
verify with the public key they already hold (HIVE_SSO_PUBLIC_KEY). The
signed body is bound to hive_id (defeats cross-hive replay) and a per-hive
monotonic seq/timestamp (defeats rollback replay). The signature is
detached in the X-Hive-Heartbeat-Signature header, computed over the exact
body bytes, so the spoke re-hashes what it received rather than re-encoding
JSON — following pkg/delegation/token.go's authenticate-before-parse order.

Rollout is staged and safe by default. Spokes default to log-only
(HIVE_HEARTBEAT_VERIFY=log): verify, log/metric on failure, still accept.
Enforcement is opt-in (HIVE_HEARTBEAT_VERIFY=enforce) and a spoke rejects
unsigned/mis-bound/stale responses only after it has accepted one valid
signed response (trust-on-first-signed), so a spoke never hard-fails
against a hub that has not yet shipped signing. Operators upgrade hubs
before enforcing on spokes.

New pkg/spoke holds the shared signing contract and the spoke-side
verifier; pkg/hub signs on the response path and verifies on the client
path. Updates src/docs/security-self-assessment.md to mark the #7082
threat mitigated and state the new hub↔spoke trust model.

Fixes #7082

Signed-off-by: Andrew Anderson <andy@clubanderson.com>
@sherine-k sherine-k added the tag/security-and-compliance TAG Security and Compliance label Sep 16, 2026
@jeefy jeefy added needs-kind Indicates an issue or PR that is missing an issue type or kind (a kind/foo label) needs-triage Indicates an issue or PR that has not been triaged yet (has a 'triage/foo' label applied) labels Sep 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-kind Indicates an issue or PR that is missing an issue type or kind (a kind/foo label) needs-triage Indicates an issue or PR that has not been triaged yet (has a 'triage/foo' label applied) tag/security-and-compliance TAG Security and Compliance

Projects

Status: New
Status: No status
Status: No status
Status: No status

Development

Successfully merging this pull request may close these issues.

5 participants