Threat model
AI-LinkMO has two risk surfaces: the application you run locally (a CLI, a FastAPI backend, a Svelte web UI, and governance frameworks encoded for the AI Risk Ontology), and this GitHub repository’s automation. SECURITY.md is the policy: how to report, and what holds each surface. This page gives the reasoning for what this repository owns. The librarian, sync, release and website workflows are copies of the ktl-sidecar templates in knowledge-trust-ladder, whose design is documented once in its threat model.
Running the application
Section titled “Running the application”Nothing here is built to be exposed to a network.
- Run on localhost. The API binds for local development and assumes a trusted caller. If you must expose it, put it behind a reverse proxy that authenticates every request; nothing in this repository does that for you.
- Treat
byo/data/andgraph/as writable. The write paths below rewrite files there in place. Run against a checkout you cangit checkout --back, not a directory holding anything you care about. - Framework encodings are interpretations, not authority. No encoding under
byo/has been checked by a named person against its framework’s published text. Do not make a compliance decision from this data without reading the source; AI_COVENANT.md says why this matters more here than in most projects.
Known limitations, accepted for now
Section titled “Known limitations, accepted for now”These are documented rather than fixed because this is a demo and the affected shapes are expected to change. They are not vulnerabilities, and reporting them as such tells us nothing new, but each becomes one the moment the application runs anywhere but localhost.
- The write endpoints are unauthenticated.
PUT /byo, andexport=trueon/graphand/crosswalk, change files underbyo/data/andgraph/with no credential. The contract the server publishes at/openapi.jsondeclares anX-API-Keysecurity scheme on the upload (it comes from the overlay inai-risk-ontology-linkmo/exposure.yaml), but the server does not enforce it: the contract describes the intended shape, not current behaviour. Enforcing it, and rejecting writes outside the two data directories, is the first hardening step planned. - The web UI’s login is a persona picker, not authentication. It selects which role’s view to render; it checks nothing and grants nothing.
- The UI fetches persona avatars from
ui-avatars.com, a third-party origin, at render time. Offline or air-gapped use shows broken images, and that origin sees your traffic. - No CodeQL or dependency-vulnerability scanning is configured for the Python and JavaScript trees. Dependabot keeps versions current (
.github/dependabot.yml), which is not the same thing. It is worth adding once the API’s shape settles. graph/holds committed generated output (about 2 MB of YAML and CSV). It is regenerable, and the export commands rewrite it in place; treat it as a build artefact that happens to be tracked, not as reviewed content. The Cypher export is fetched from ai-atlas-nexus on request and is not tracked.
Repository hardening
Section titled “Repository hardening”- Every GitHub Action is pinned to a reviewed commit SHA rather than a floating tag, in every workflow.
.github/dependabot.ymlkeeps those pins current, alongside the application’s Python tree, the.lokf/sidecar’s separate tree, and the frontend’s. - Every workflow declares
permissions: {}at the top level, so each job opts into only the scopes it needs, and runs Step Security’s harden-runner in audit mode to monitor runner egress. semantic-release.yml’sreleasejob, which pushes a commit and tag tomainand publishes the release, runs in thereleaseGitHub Environment, which admits onlymain. Required reviewers on that Environment are not configured at present, so a qualifying merge releases with no human step; Settings → Environments is where to add them. Itsnpm installof the pinned release tooling runs with--ignore-scripts.mainis protected, but not by a merge gate. A ruleset blocks deletion and force-pushes and requires linear history. A rule requiring pull requests or passing status checks is deliberately not enabled: rulesets apply to direct pushes as well as merges, and the release workflow’s own commit tomaincannot be exempted, because a bypass list accepts roles, teams, GitHub Apps and Dependabot, andgithub-actions[bot]is none of those. What gates a change here is access control: only the maintainer can write to this repository, CI runs on every pull request, and its result is read before merging.- Secret scanning, push protection and Dependabot security updates are enabled.
The ruleset, the Environments and the scanning are repository settings, not files in the tree. Nothing in CI can assert that they are still in force, so keeping them on is the maintainer’s responsibility.
The librarian workflow: what is inherited, what this repository owns
Section titled “The librarian workflow: what is inherited, what this repository owns”.github/workflows/knowledge-librarian.yaml and .lokf/scripts/knowledge-librarian.sh are copies of the template, and their security design is the template’s:
- how the job split keeps the agent away from any write-scoped token, and why the
publishjob’s own checks (path confinement derived from the patch itself, refusal of any change to a person’s record) are the backstop rather than the wrapper’s: Prompt-injection guards; - how the
ktl-librarianskill treats.lokf/feedback.md, and everything else it did not author, as content to inspect and never as instructions to follow: the same section; - why a skill’s stated scope is prose, not a permission, when you run it interactively: Interactive use: scope is advisory, not enforced;
- why the review pull request it opens does not trigger this repository’s
pull_requestchecks, and what stands in for them: Repository hardening.
The workflow runs in three jobs. earlier runs no agent and holds only pull-requests: read: it reads what became of this workflow’s earlier pull requests, so a scheduled run opens no second one beside an open one and does not propose again what a person declined. refresh runs the agent, which is third-party code, with contents: read and persist-credentials: false, so no git credential is on disk while it executes. The agent’s one output is .lokf/patch.yaml, which knowledge-apply.sh applies, and refresh hands the result to publish as a patch artifact. Only publish, which runs no agent code, holds contents: write and pull-requests: write.
Moving the pin (TRUST_LADDER_SKILLS_REF and the commit TRUST_LADDER_SKILLS_SHA beside it) changes only the skill the scheduled run installs. The workflows and the files under .lokf/scripts/ and .lokf/m365/ are copies of skills/ktl-sidecar/templates/ that knowledge-sync.yaml moves with the pin, from the release it names, and knowledge-preflight.sh reports drift on its copies line where a sidecar is installed to compare against. What this repository does own, and is accountable for:
- The deployed copy. Both files carry every job and check the template does. The wrapper is byte-identical to its template, and the workflow differs from its template only in the pin the install step names. Anything further edited locally becomes this repository’s responsibility rather than the template’s;
diffagainstskills/ktl-sidecar/templates/in a checkout of knowledge-trust-ladder to confirm. - The switches. The agent step runs only while the
KNOWLEDGE_LIBRARIAN_ENABLEDrepository variable istrue.AGENT_CLIchooses which non-interactive agent runs, never what command, and its credential, theAGENT_API_KEYsecret or the job token, reaches only the agent. The workflow triggers only onscheduleandworkflow_dispatch. All three are readable in this repository’s own YAML. - The surfaces. What the librarian reads here is
README.md,llms.txt,byo/content and the.lokf/knowledge/bundle, and.lokf/feedback.md, the one input that can come from someone with no repository access. The same files are prompt-injection surfaces whenever any agent is asked to act on repository text, external URLs or reader feedback. - The consequence if the inherited guards fail. The agent holds no write-scoped token, so the worst outcome is a proposed patch confined to the knowledge bundle,
.lokf/feedback.mdand.lokf/questions.md, applied by a job that runs no agent code, arriving as a pull request that only the maintainer can merge, after reading it. Nothing on that path reaches a published release or the application code. - The attribution gate.
.github/workflows/knowledge-registrar.yamlcarries the template’svalidate,provenanceandattestationjobs, unchanged, so ahuman:event that a change adds, alters or removes must be backed by that person’s evidence GitHub holds: an approving review, or a verified signature on the commit. Human attribution has the design and its stated limits.