This is the handover document. It is the contract between the PoC you have been given and the production system your team will build. Read it end-to-end before changing anything.
It is organised around nine integration touch points. Each touch point has a Goal, the concrete What to do (exact files and environment variables), the Decisions to make, and a PoC shortcut vs production note that tells you what the demo fakes and what production must do instead.
Companion docs:
- ARCHITECTURE.md — how the four services fit together.
- ../ENTRA-APP-SETUP.md — step-by-step Entra app registration and the SharePoint per-site grant (referenced from §3).
- ../README.md — local quickstart and the demo script.
The PoC proves one thing end-to-end: a blue-collar staff member can log in with a phone number, ask an HR or safety question in plain English, and get a cited answer drawn only from the documents their job role is allowed to see — with a warehouse worker provably unable to retrieve payroll documents. It runs today on one machine against bundled sample documents, a mock login, and a local AI model, so you can watch the whole workflow before wiring in real systems.
Integration means swapping each fake for the real Wiseway equivalent without changing the workflow or the security model: replace the mock login with Wiseway SSO, point the document tool at the real SharePoint HR/SOP library, host the AI model on approved Australian infrastructure, and run the four services on Wiseway's own platform. The access control that makes a warehouse worker unable to read payroll is enforced in one place — the document-search tool — and that does not change when you integrate; it only gets fed real identities and real documents.
| # | Touch point | What the team builds | Files / env affected | Effort / owner |
|---|---|---|---|---|
| 1 | Auth / login → Wiseway SSO | Repoint LibreChat OIDC at Wiseway's OAuth2/OIDC endpoints; map the role claim to the business role the tool reads |
deploy/.env (OPENID_*), deploy/seed-roles.sh / Mongo users + roles collections |
Medium — IdP/SSO team + integration |
| 2 | Hosting | Run LibreChat, MongoDB, the doc-search tool and the LLM on Wiseway infra; TLS, persistence, scaling | docker-compose.yml → orchestrator manifests; deploy/librechat.yaml |
Large — platform/infra |
| 3 | SharePoint (machine login) | Register the Entra app, grant Sites.Selected read on the HR/SOP site, flip the backend to graph |
deploy/.env (TENANT_ID, GRAPH_*, SHAREPOINT_*), docker-compose.yml (WISEWAY_DOC_BACKEND), ENTRA-APP-SETUP.md |
Medium — M365 admin + integration |
| 4 | The LLM / model | Choose and host an AU-region model (self-host or cloud); point LibreChat at it | deploy/librechat.yaml (endpoints.custom) |
Medium/Large — ML/platform + privacy |
| 5 | Roles & access policy | Define the role catalogue and role→category map; structure SharePoint into per-category folders | mcp-doc-search/roles.yaml, SharePoint folder layout, HR/IdP role source |
Medium — HR + security + integration |
| 6 | Citation links / doc-proxy | Build a small proxy so cited links open for login-less staff | new service + mcp-doc-search (link rewrite) |
Small/Medium — integration |
| 7 | Audit / logging | Pipe the tool's JSON audit lines to the SIEM; set retention + PII handling | mcp-doc-search/server.js (already logs), log pipeline |
Small — security/platform |
| 8 | Secrets management | Move every secret into a vault; rotate the Graph client secret on expiry | deploy/.env → vault, orchestrator secret store |
Small/Medium — platform/security |
| 9 | Security review | Replace mock IdP + demo certs + demo signing key; pen-test the integrated system | mock-idp/ (deleted), deploy/.env crypto secrets |
Medium — security |
Where the demo and the older sub-folder READMEs disagree, this guide and the code are authoritative. A few older notes drifted from the current code (the env template is
deploy/.env.example, not.env.wiseway.example; Mongo data is a named volumemongo-data, notdeploy/data-node; brand assets mount under/app/client/dist/assets; the in-chat icon is a PNG; the graph backend lists + extracts rather than calling Graphsearch(q=)). Those points are called out below where they matter.
Replace the mock phone+PIN identity provider (mock-idp/, service wiseway-idp)
with Wiseway's real SSO so staff sign in with their actual corporate/merged
staff+warehouse credentials, and so the business role that drives document
access flows from a trusted source rather than a hand-seeded Mongo field.
1. Stand down the mock IdP. Delete the wiseway-idp service from
docker-compose.yml (and the depends_on: wiseway-idp line under librechat),
and remove the mock-cert volume mount and the NODE_EXTRA_CA_CERTS env line that
exist only to trust the mock IdP's self-signed certificate:
# docker-compose.yml — remove from the librechat service once on real SSO:
# - NODE_EXTRA_CA_CERTS=/app/certs/idp.crt
# - ./mock-idp/certs/idp.crt:/app/certs/idp.crt:roThe whole mock-idp/ directory can then be deleted from the deployment image.
2. Repoint LibreChat's OIDC at the four Wiseway OAuth2/OIDC endpoints. All of
this lives in deploy/.env under the OpenID Connect block. LibreChat uses
standard OIDC discovery, so you primarily set the issuer; LibreChat then reads
/.well-known/openid-configuration to find the authorize/token/jwks/userinfo
URLs. Provide the issuer that exposes these four endpoints:
| Wiseway endpoint | Role in the flow |
|---|---|
/oauth2/authorize |
browser is redirected here to log in |
/oauth2/token |
LibreChat exchanges the auth code for tokens |
/oauth2/jwks |
LibreChat fetches signing keys to validate the ID token |
/userinfo |
LibreChat reads user claims (fallback if not in the ID token) |
Set in deploy/.env:
OPENID_ISSUER=https://login.wiseway.example/ # the real Wiseway SSO issuer (CA-signed HTTPS)
OPENID_CLIENT_ID=<client id Wiseway issues for LibreChat>
OPENID_CLIENT_SECRET=<from vault — see §8>
OPENID_CALLBACK_URL=/oauth/openid/callback # full URL is DOMAIN_SERVER + this
OPENID_SCOPE=openid profile email # add the scope that carries the role claim
OPENID_USERNAME_CLAIM=preferred_username
OPENID_NAME_CLAIM=name
OPENID_EMAIL_CLAIM=email
OPENID_AUTO_REDIRECT=true # skip LibreChat's own login screen
OPENID_BUTTON_LABEL=Wiseway Staff Login
OPENID_SESSION_SECRET=<fresh 64-hex random — see §8>Also set the public base URLs LibreChat uses to build the callback redirect_uri
(in deploy/.env): DOMAIN_SERVER and DOMAIN_CLIENT must be the real public
HTTPS URL of the app, and that exact redirect_uri
(<DOMAIN_SERVER>/oauth/openid/callback) must be registered on the Wiseway SSO
client. Keep ALLOW_EMAIL_LOGIN=false, ALLOW_REGISTRATION=false,
ALLOW_SOCIAL_LOGIN=true so SSO is the only way in.
3. Make the IdP emit a role claim. The single most important integration
requirement: the Wiseway IdP must return each user's business role
(warehouse / driver / office / hr-admin, or whatever §5 defines) in a token
claim — ideally on the profile scope. In the mock IdP this is the role claim
(see mock-idp/server.js, claims.profile includes role). If Wiseway's IdP
calls it something else, you map it in step 4.
4. Get the role onto the Mongo user document — and mind the roles-collection
nuance. This is the subtle part. LibreChat lands every OIDC user as its
generic platform role USER. The business role is a separate string,
user.role, on the Mongo user document. That string is what LibreChat
substitutes into the {{LIBRECHAT_USER_ROLE}} template, which becomes the
X-User-Role header the doc-search tool reads (see deploy/librechat.yaml
mcpServers.wiseway-docs.headers).
In the demo, deploy/seed-roles.sh hand-writes user.role per email. You must
replace the hand-seed with automatic mapping from the IdP role claim. Two
viable paths:
- Preferred: configure LibreChat's OIDC role mapping so the claim from step 3
is written to
user.roleat login (LibreChat supports mapping an OIDC claim to the user role; map your role claim →user.role). No seed script in prod. - Bridge: keep a small reconciliation job that reads the role from the HR
system / IdP and writes
user.role(the same fieldseed-roles.shwrites), keyed by the stable user identifier (email orsub).
The roles-collection nuance — do not skip this. In LibreChat,
user.roleALSO selects the user's feature-permission set, and LibreChat looks that role up in its Mongorolescollection. Out of the box that collection contains onlyUSER(andADMIN). If you setuser.role = "warehouse"and there is nowarehousedocument in therolescollection, LibreChat has no permission set for that user and the UI can misbehave. Therefore each business role must also exist as a document in the Mongorolescollection, cloned from theUSERpermissions. In the demo this is implicitly fine because the values still resolve; in production, seed therolescollection once with one cloned-from-USERdocument per business role (warehouse,driver,office,hr-admin, …) so the business role works both as a doc-access key and as a valid LibreChat permission role.
- How does the role reach us? A token claim (cleanest) vs a lookup against the HR system at login. Pick one and own it.
- Claim name and value vocabulary. The values must match the keys in
mcp-doc-search/roles.yamlexactly (case-insensitive; the tool lowercases). If the IdP emits e.g.WAREHOUSE_OPERATOR, add a normalisation map. - Stable user key.
sub(opaque, stable) vs email (human-readable, can change). The tool only needs the role; the audit log keys onX-User-Id. - Login UX for shared/kiosk warehouse devices (session length, auto-logout).
- Shortcut: mock OIDC provider, phone+PIN against four hard-coded accounts in
mock-idp/staff.js, a throwaway committed signing key, a self-signed cert, and a manualseed-roles.shto set roles. - Production: real Wiseway SSO over CA-signed HTTPS, real credential policy
and MFA owned by the IdP, role delivered by claim, role-to-Mongo mapping
automated, and the
rolescollection pre-seeded. The mock IdP is not secure and must be removed (see §9).
Run the four moving parts — LibreChat (UI + API), MongoDB, the doc-search tool, and the LLM — on Wiseway's own infrastructure, with TLS, durable storage, backups, and the ability to scale, replacing the single-laptop Docker Compose stack.
Translate the stack. docker-compose.yml defines the topology you need to
reproduce on the target orchestrator (Kubernetes, ECS, App Service, etc.):
| Compose service | Production shape | Notes |
|---|---|---|
librechat (:3080) |
Stateless web/API deployment behind ingress + TLS | Reads config from deploy/librechat.yaml (mount as /app/librechat.yaml via CONFIG_PATH) and env from deploy/.env. Scale horizontally; it is stateless apart from Mongo. |
mongodb |
Managed/HA MongoDB with persistence + backups | The demo uses a single mongo:8.0.20 with a named volume mongo-data. Production needs real durability — this holds users, roles, and conversation history. |
wiseway-doc-search (:8000) |
Internal-only deployment | Stateless; in-memory index/cache only (rebuilds on start, 5-min TTL for the graph backend). Reachable from LibreChat by service name; not publicly exposed. |
| Ollama (host) | The LLM — see §4 | In the demo this runs natively on the host at http://host.docker.internal:11434/v1/; in production it becomes a hosted endpoint. |
Wiring you must preserve:
- LibreChat → doc-search over
http://wiseway-doc-search:8000/mcp(or the prod equivalent service DNS). The hostname must be indeploy/librechat.yamlmcpSettings.allowedDomainsor LibreChat's SSRF guard blocks it ("Domain … is not allowed"). - LibreChat → LLM
baseURLindeploy/librechat.yamlendpoints.custom. - LibreChat → MongoDB via
MONGO_URIindeploy/.env. - LibreChat → Wiseway SSO (§1) and doc-search → SharePoint (§3) need egress from the cluster to those endpoints.
Networking / exposure rules:
- Only LibreChat is public (behind ingress + TLS). MongoDB, doc-search, and the LLM are internal.
- Keep the
X-Mcp-Keyshared-secret gate (§7/§8) so even on the internal network only LibreChat can call the tool. - Drop the
extra_hosts: host.docker.internalentries and theNODE_EXTRA_CA_CERTS/self-signed-cert mounts — those are local-dev artefacts.
- Orchestrator and ingress/TLS stack (cert source, WAF in front of LibreChat).
- MongoDB: managed service vs self-run replica set; backup cadence and retention for conversation history (which is PII — coordinate with §7).
- Scaling: LibreChat and doc-search scale horizontally; the LLM is the capacity-and-cost driver (§4). Doc-search holds an in-memory index per replica — fine, but each replica warms independently.
- Config delivery: how
librechat.yamland env reach the pods (ConfigMap + secret store, per §8).
- Shortcut: single-host Docker Compose; Mongo with no backups in a local
volume; everything on one bridge network;
host.docker.internalhops; LLM on the laptop's GPU. - Production: orchestrated, TLS-terminated, horizontally scalable services; HA Mongo with backups; private networking with only LibreChat exposed; LLM as a managed/hosted endpoint.
Switch the doc-search tool from the bundled sample documents to the real Wiseway HR/SOP SharePoint library, read by a single least-privilege machine identity — because login-less staff have no SharePoint accounts of their own.
Follow ENTRA-APP-SETUP.md for the click-by-click; the summary:
1. Register the Entra app (the "machine login"). A single-tenant app
registration with a client secret. This identity signs in as itself (app-only /
client credentials), not as any staff member. From it you get TENANT_ID
(directory id) and GRAPH_CLIENT_ID (application id); create a client secret for
GRAPH_CLIENT_SECRET.
2. Grant Sites.Selected read on ONLY the HR/SOP site. Add the Microsoft
Graph application permission Sites.Selected and admin-consent it. By itself
that grants zero sites. Then grant this app read on the one target site. The
Entra/Azure portal cannot do a per-site grant — you must call Graph directly
(Graph Explorer or PowerShell New-MgSitePermission):
POST https://graph.microsoft.com/v1.0/sites/{site-id}/permissions
{ "roles": ["read"],
"grantedToIdentities": [ { "application":
{ "id": "<GRAPH_CLIENT_ID>", "displayName": "wiseway-doc-search" } } ] }Blast radius = exactly one SharePoint site, read-only.
3. Set the Graph environment and flip the backend. Put the real credentials in
deploy/.env (never committed — see §8). The variable names the code actually
reads (mcp-doc-search/backends/graph.js):
TENANT_ID=<directory/tenant id>
GRAPH_CLIENT_ID=<application/client id>
GRAPH_CLIENT_SECRET=<secret value — from vault>
SHAREPOINT_HOSTNAME=<tenant>.sharepoint.com # default in code: contoso.sharepoint.com
SHAREPOINT_SITE_PATH=sites/<YourHRSite> # default in code: sites/YourHRSite
SHAREPOINT_ROOT_FOLDER=<root subtree to index> # default in code/compose: Wiseway-Demo
# SHAREPOINT_DRIVE_ID=<optional — pin a specific document library drive>Flip the backend selector. In docker-compose.yml it is currently
WISEWAY_DOC_BACKEND=local on the wiseway-doc-search service; set it to
graph (or set it in deploy/.env):
# docker-compose.yml, wiseway-doc-search service:
- WISEWAY_DOC_BACKEND=graphRecreate the service so it reads the new env (backend.js selects the backend at
startup): docker compose up -d --force-recreate wiseway-doc-search.
4. Understand how the graph backend actually reads SharePoint (so you size and
debug it correctly). It does not use Graph's search(q=) endpoint —
that endpoint returns 500 generalException under app-only Sites.Selected.
Instead graph.js:
- resolves the site → its default document-library drive;
- recursively lists every file under
SHAREPOINT_ROOT_FOLDER; - downloads each file and extracts text (
.docxvia themammothlibrary;.txt/.md/.csvas plain text; other types indexed by filename only); - caches the extracted corpus in memory (5-minute TTL,
GRAPH_INDEX_TTL_MS) and ranks locally with a simple term-frequency score.
Implications: the first query after a cache miss pays the cost of listing +
downloading + extracting the whole subtree, so keep the indexed subtree scoped and
sized sensibly, and expect a cold-start delay per replica. PDFs and spreadsheets
are matched by filename only (no body text) — convert to .docx/.md if their
contents need to be searchable.
5. Category comes from the folder. The document category (which drives the
role gate, §5) is derived from the file's parent folder name: a path segment
containing payroll → payroll, safety/whs → safety,
sop/procedure → sop, hr/human → hr, otherwise default hr. This is
why SharePoint must be foldered by category — see §5.
- Which site / library is the source of truth for HR/SOP/safety/payroll docs.
- Secret vs certificate for the app credential (certificate preferred for production; the code currently uses a client secret).
- Indexed subtree size vs cold-start latency and download cost; consider a warmed/persistent index if the corpus is large.
source_urlreachability — the citation URL is the real SharePointwebUrl, which will not open for login-less staff (see §6).
- Shortcut:
WISEWAY_DOC_BACKEND=localreads six markdown files fromsample-docs/(BM25 via MiniSearch); no SharePoint, no credentials. The sample docs' front-matter even encodes the eventual SharePointsource_urls as the contract for the real library. - Production:
WISEWAY_DOC_BACKEND=graphagainst the real site via the least-privilege machine identity, with category-foldered content (§5), the client secret in a vault and on a rotation schedule (§8), and a doc-proxy for citations (§6).
Replace the laptop's local Ollama model with a production model hosted on approved Australian infrastructure, because staff questions can contain personal information and the Australian Privacy Act / data-residency rules apply to where that text is processed.
The model is configured in deploy/librechat.yaml under endpoints.custom. The
demo entry points at Ollama on the host:
endpoints:
custom:
- name: "Ollama"
apiKey: "ollama"
baseURL: "http://host.docker.internal:11434/v1/" # <- change this
models:
default: ["qwen2.5:7b"]To integrate, point baseURL at the production model's OpenAI-compatible
endpoint and set apiKey (from the vault, §8), then list the served model
name(s) under models.default. Two hosting shapes:
- Self-hosted on Wiseway GPUs (vLLM or Ollama on a GPU node/cluster): keeps
every query inside Wiseway's network; you own the GPUs and scaling. Point
baseURLat the internal inference service. - AU-region managed model (e.g. AWS Bedrock Sydney / Azure OpenAI
Australia East): no GPUs to run, data stays in-region. If the managed service
is not natively OpenAI-compatible, front it with a small adapter and point
baseURLat that.
Whichever you choose, confirm the model is a competent tool-caller — the whole
workflow depends on the model reliably calling the search/fetch MCP tools and
then citing. qwen2.5:7b was chosen for the demo specifically because it
tool-calls reliably; validate any replacement against the demo script before
trusting it.
The agent's citing instruction is load-bearing and lives in the agent's system
prompt (see the example prompt in deploy/librechat.yaml's comment above
modelSpecs) — re-use it, since smaller local models will not auto-cite without
the explicit instruction.
- Self-host vs AU-cloud — the core call, driven by data-residency posture, GPU appetite, and cost. Anything that sends query text outside Australia must clear privacy review first.
- Model + size — quality vs latency vs GPU cost; must tool-call reliably.
- Capacity/scaling for concurrent staff at shift change.
- Whether the chosen model still cites well with the existing system prompt.
- Shortcut:
qwen2.5:7bon Ollama, on the developer's laptop GPU, reached viahost.docker.internal. - Production: an approved, AU-resident, appropriately sized model on Wiseway-
managed infra, with the
baseURL/apiKeyrepointed inlibrechat.yamland the credential in a vault.
Turn the demo's four-role map into Wiseway's real role catalogue and category policy, and structure SharePoint so that the folder-derived category actually gives meaningful role separation.
1. Define the role → category map in mcp-doc-search/roles.yaml. This file is
the security policy in one place. The demo:
roles:
warehouse: [hr, sop, safety]
driver: [hr, sop, safety]
office: [hr, sop]
hr-admin: [hr, sop, safety, payroll]Replace these with Wiseway's real roles and the categories each may read. Rules
the code enforces: roles are matched case-insensitively; a role not listed here
gets an empty allow-set and can read nothing (fail-closed); categories are
free-form strings but must match the folder-derived categories from §3
(hr, sop, safety, payroll, plus any you add).
2. Structure SharePoint into per-category subfolders. Because the graph backend derives a document's category from its parent folder name (§3 step 5), the real site must be foldered so each document lands in the right category — e.g. under the indexed root:
<SHAREPOINT_ROOT_FOLDER>/
HR/ -> category hr
SOP/ -> category sop
Safety/ -> category safety (also matches "WHS")
Payroll/ -> category payroll
A document in a folder that matches none of the keywords defaults to hr. If
everything sits in one flat folder, every document becomes hr and role
separation collapses — so the folder layout is a real security requirement, not
cosmetic. (Note the sample-docs source_urls currently point at a single
Wiseway-Demo/HR-AU/ folder; production must fan documents out into the
category subfolders above.)
3. Map roles from the system of record. Decide where each staff member's role
comes from (HR system, the IdP claim from §1) and ensure those role values match
the roles.yaml keys (add a normalisation step if the source vocabulary differs).
- The role catalogue and role→category matrix — an HR/security policy decision, not just config. Get it signed off.
- Category taxonomy — keep the four, or add (e.g.
compliance,induction). Every new category needs both a folder keyword rule and aroles.yamlentry. - Folder vs metadata for category — the current code uses folder name; if
Wiseway prefers SharePoint columns/metadata, that's a
graph.jsderiveCategory()change to plan. - Default-category policy — is "unknown folder →
hr" acceptable, or should unknown fail closed?
- Shortcut: four roles, four categories, category inferred from a single demo
folder; role values hand-seeded to match
roles.yaml. - Production: the real role catalogue, SharePoint foldered by category, roles
sourced from the IdP/HR system and normalised to the
roles.yamlkeys.
Make every cited source link actually open for a staff member who has no SharePoint account — the unavoidable trade-off of the app-only machine login.
With the graph backend, the citation source_url is the document's real
SharePoint webUrl. Staff authenticate to the app via SSO but have no
SharePoint identity, so clicking that link prompts them for a Microsoft login
they don't have, and the link fails. Two fixes:
- Recommended — a small doc-proxy. Build a lightweight service that, given a
doc_id, streams the file using the same machine login the doc-search tool already uses (Graph app-only download). Rewrite citation links to point at the proxy (https://assistant.wiseway.example/doc/<doc_id>) instead of the rawwebUrl. The proxy must re-check the caller's role against the sameroles.yamlpolicy before streaming, so it can't be used to bypass the gate. The rewrite happens where hits are formatted (mcp-doc-search/server.js, thesearch/fetchtext blocks). - Alternative — inline snippets only. Render the cited snippet/body inline and drop the outbound link entirely, so there is nothing to fail to open. Simpler, but staff can't see the full source document.
- Proxy vs inline-only (proxy preserves "open the real document"; inline is less to build and operate).
- If proxy: its own authn (must trust the same SSO session) and its own role
re-check (never trust the requested
doc_idalone). - Audit proxy downloads alongside the tool's audit log (§7).
- Shortcut: local backend
source_urls are illustrative SharePoint URLs that don't need to open; the demo is about showing a cited link, not serving the file. - Production: a role-aware doc-proxy (or inline snippets) so citations resolve for login-less staff without weakening the role gate.
Get the document-access audit trail into Wiseway's SIEM, with defined retention and PII handling.
The tool already emits a structured JSON audit line per call — see
audit() in mcp-doc-search/server.js. Each line includes an ISO timestamp,
tool (search/fetch), role, user_id, the query or doc_id, the backend
name, and crucially returned_doc_ids (exactly which documents were surfaced).
Rejected calls (bad/missing X-Mcp-Key) and errors are logged too. It writes to
stdout, so:
- Ship stdout to the SIEM via the platform's normal log pipeline (the container already does the work; you just collect it).
- Set retention to meet Wiseway's policy for access logs.
- Handle PII deliberately. The
queryfield is free text a staff member typed and can contain personal information;user_idis an identifier. Decide whether to store the raw query, redact/hash it, or shorten its retention — and document that decision. - If you build the doc-proxy (§6), feed its download events into the same trail.
- Retention period and storage location for the audit log.
- PII posture on the
queryfield (store / redact / hash / short-TTL). - Alerting — e.g. flag bursts of
reject(bad-key) events as a tamper signal.
- Shortcut: JSON lines to the container's stdout; nobody collects them.
- Production: stdout shipped to the SIEM, retention set, query-field PII handled per policy, alerts on rejects.
Get every secret out of deploy/.env and into a managed vault, and put the Graph
client secret on a rotation schedule before it expires.
Inventory the secrets the system uses and move each into the orchestrator's secret store / a vault, injecting them as env at runtime instead of a committed-adjacent file. The secrets:
| Secret (env var) | Used by | Notes |
|---|---|---|
GRAPH_CLIENT_SECRET |
doc-search graph backend | Rotate before expiry (~180 days). Prefer a certificate. |
JWT_SECRET, JWT_REFRESH_SECRET, CREDS_KEY, CREDS_IV |
LibreChat crypto | The .env.example ships REPLACE_ME_* placeholders — generate fresh per the one-liners in deploy/.env.example and never reuse the examples. |
OPENID_CLIENT_SECRET, OPENID_SESSION_SECRET |
OIDC login | Real values from Wiseway SSO + fresh session secret. |
WISEWAY_MCP_SHARED_SECRET |
LibreChat ↔ doc-search gate | The X-Mcp-Key. Generate a strong value; both sides must match. |
deploy/.env is already gitignored (see .gitignore), and the repo ships only
placeholders — keep it that way: no real secret ever lands in a tracked file.
- Which vault (Key Vault / Secrets Manager / sealed secrets / etc.) and how it injects into pods.
- Rotation automation for
GRAPH_CLIENT_SECRET(and ideally a move to a certificate); rotation runbook for the LibreChat crypto keys and the MCP key. - Per-environment secrets (dev/test/prod) and least-privilege access to them.
- Shortcut: all secrets in a gitignored
deploy/.env; the MCP key and OIDC client secret are deliberately weak demo placeholders. - Production: every secret in a vault, injected at runtime, rotated on schedule, scoped per environment.
Make sure nothing from the demo's "make-it-work-on-a-laptop" shortcuts survives into production, and that the integrated system is independently tested.
- The mock IdP (
mock-idp/) is not security. It authenticates four hard-coded accounts against plaintext PINs inmock-idp/staff.js. Delete it once on real SSO (§1). - The demo signing key is public.
mock-idp/server.jscontains a throwaway RSA private key committed on purpose so the JWKS is stable across restarts. It is published in this repo — it must never be used anywhere real. Real signing keys belong to Wiseway SSO. - The self-signed certs are throwaway. Generated by
mock-idp/gen-certs.sh; trusted only viaNODE_EXTRA_CA_CERTSfor local dev. Production uses CA-signed TLS everywhere; remove that cert mount and env line. - Demo crypto/secret placeholders in
deploy/.env.example(REPLACE_ME_*,wiseway-demo-secret,wiseway-mcp-shared-secret) must be regenerated as real vault-held secrets (§8). - The agent is built once in the UI. The
Wiseway HR & SOP Assistantagent cannot be declared in YAML; it is created in LibreChat's Agent Builder and pinned byagent_idindeploy/librechat.yamlmodelSpecs. Recreate it per environment and confirm the citing system prompt and thesearch/fetchtools are attached. (Branding: the in-chat icon comes frommodelSpecs.iconURLand must be a PNG — the demo uses/assets/icon-192x192.png; an SVG that wraps a raster image fails to render. Logo/favicons are mounted over/app/client/dist/assets.)
- The role gate is enforced in the tool, not the model — confirm a low- privilege role provably cannot retrieve a restricted category (repeat the warehouse-vs-payroll demo against real data).
- The
X-Mcp-Keygate is in place so only LibreChat can reach doc-search. - The doc-proxy (§6), if built, re-checks role before streaming.
- Secrets are vaulted (§8) and the audit trail flows to the SIEM (§7).
- Pen-test scope and timing — the integrated system (SSO, app, tool, SharePoint, proxy) must be penetration-tested before go-live.
- Privacy/DPIA sign-off for query data and model hosting (ties to §4).
- Threat model review of the header-trust design under the real network.
- Shortcut: mock IdP, public demo signing key, self-signed certs, placeholder secrets — all fine on a laptop, none acceptable in production.
- Production: all demo auth artefacts removed, real SSO and TLS, vaulted secrets, and an independent pen-test + privacy sign-off before go-live.
- §1 Mock IdP removed; LibreChat
OPENID_*pointed at Wiseway SSO (/oauth2/authorize,/oauth2/token,/oauth2/jwks,/userinfo); CA-signed HTTPS issuer;redirect_uriregistered on the SSO client. - §1 IdP emits a role claim; role auto-maps to Mongo
user.role; each business role exists as a cloned-from-USERdocument in the Mongorolescollection. - §2 LibreChat + Mongo + doc-search + LLM running on Wiseway infra; only LibreChat public; TLS at the ingress; Mongo persistent + backed up.
- §3 Entra app registered;
Sites.Selectedread granted per-site on the real HR/SOP site;TENANT_ID/GRAPH_CLIENT_ID/GRAPH_CLIENT_SECRET/SHAREPOINT_*set;WISEWAY_DOC_BACKEND=graph. - §4 Approved AU-region model hosted;
endpoints.custom.baseURL/apiKeyrepointed; tool-calling + citation validated against the demo script. - §5
roles.yamlreflects the real role→category policy; SharePoint foldered by category; role values normalised to theroles.yamlkeys. - §6 Doc-proxy (role-rechecking) live, or inline snippets — citations open for login-less staff.
- §7 Tool audit lines shipped to the SIEM; retention + query-PII policy set.
- §8 All secrets in a vault;
GRAPH_CLIENT_SECRETrotation scheduled; no real secret in any tracked file. - §9 Demo signing key/certs/placeholders gone; warehouse-vs-payroll gate re-verified on real data; pen-test + privacy sign-off complete.
The local demo is intentionally insecure where insecurity buys convenience: the mock IdP is not real authentication, the OIDC signing key is a throwaway committed in the repo, the TLS certs are self-signed, and the shared secrets are demo placeholders. None of these may reach production. Before go- live, remove every demo auth artefact (§1, §9), move all secrets to a vault (§8), confirm the role gate holds in the tool against real data, and have the integrated system independently penetration-tested and cleared by privacy review.