agentlas 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/engine/agentlas-workforce.cjs +82 -41
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.4 — 2026-07-27
|
|
4
|
+
|
|
5
|
+
**The hub boundary stops guessing.** Owner decision after live runs: rules
|
|
6
|
+
that infer private data from shape were removed rather than tuned, because
|
|
7
|
+
each one refused work orders whose flagged phrase *was* the task — a slash
|
|
8
|
+
between Korean words read as a file path, "멱등키 설계" read as a credential
|
|
9
|
+
assignment, "고객 ID를 조회하는 API" read as a labeled identifier. No repair
|
|
10
|
+
was possible, so the request simply died.
|
|
11
|
+
|
|
12
|
+
- Gone: path inference from a slash, labeled-identifier inference, UUID/IP/
|
|
13
|
+
phone shape matching, keyword-adjacency credential matching.
|
|
14
|
+
- Kept: forms that can only be one thing — issuer-prefixed provider tokens,
|
|
15
|
+
PEM headers, JWTs, credentials embedded in a URL, email addresses.
|
|
16
|
+
- Those are now **redacted from the outgoing text instead of refusing the
|
|
17
|
+
run**, and the redaction is printed, so a pasted secret never leaves the
|
|
18
|
+
machine and the task still proceeds. `AGENTLAS_HUB_BOUNDARY=off` sends
|
|
19
|
+
text verbatim.
|
|
20
|
+
- Upload and packaging are unchanged: they already block by file identity
|
|
21
|
+
(`.env*`, `*.pem/key/p12`, `credentials*`, `id_rsa`), which is fact rather
|
|
22
|
+
than inference.
|
|
23
|
+
|
|
24
|
+
Shared fixtures (`privacy-guard-fixtures/`) and `scripts/sync-privacy-guard.sh`
|
|
25
|
+
now pin this contract across the terminal engine and the server.
|
|
26
|
+
|
|
3
27
|
## 1.0.3 — 2026-07-27
|
|
4
28
|
|
|
5
29
|
Live `workforce` runs surfaced four defects that no unit gate could reach.
|
|
@@ -806,49 +806,79 @@ function decodedHubText(value) {
|
|
|
806
806
|
return text;
|
|
807
807
|
}
|
|
808
808
|
|
|
809
|
+
/*
|
|
810
|
+
* Hub-bound text hygiene — SELF-PROVING FORMS ONLY, and it masks instead of killing.
|
|
811
|
+
*
|
|
812
|
+
* Owner decision 2026-07-27, after live runs: every GUESSING rule (a slash after
|
|
813
|
+
* a word = a path, "key"/"secret" near a value = a credential, digit runs =
|
|
814
|
+
* a phone, hex groups = a UUID/IP) produced false positives that killed real
|
|
815
|
+
* requests with no repair available — the flagged phrase WAS the task
|
|
816
|
+
* ("진단/멱등키", "멱등키 설계"). Guessing is removed, not tuned.
|
|
817
|
+
*
|
|
818
|
+
* What remains is a narrow certainty: strings whose form can only be one thing
|
|
819
|
+
* (provider token with its issuer prefix, PEM header, JWT, credentials in a
|
|
820
|
+
* URL, an email address). Those are redacted from the outgoing text rather than
|
|
821
|
+
* refused, so a paste accident never leaves the machine and the run continues.
|
|
822
|
+
* Set AGENTLAS_HUB_BOUNDARY=off to send text verbatim.
|
|
823
|
+
*/
|
|
824
|
+
const HUB_REDACTION_PATTERNS = [
|
|
825
|
+
["secret_provider_token", /\b(?:sk-[A-Za-z0-9_-]{20,}|gh[pousr]_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,}|AKIA[0-9A-Z]{16}|xox[baprs]-[A-Za-z0-9-]{10,})\b/g],
|
|
826
|
+
["secret_private_key", /-----BEGIN (?:RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----/gi],
|
|
827
|
+
["secret_bearer_token", /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}/gi],
|
|
828
|
+
["secret_jwt", /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g],
|
|
829
|
+
["secret_credential_url", /\b[A-Za-z][A-Za-z0-9+.-]*:\/\/[^\s/:@]+:[^\s/@]+@[^\s/]+/gi],
|
|
830
|
+
["email", /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi],
|
|
831
|
+
];
|
|
832
|
+
|
|
833
|
+
function hubBoundaryEnabled(env = process.env) {
|
|
834
|
+
return String(env.AGENTLAS_HUB_BOUNDARY || "").trim().toLowerCase() !== "off";
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
/** Redact self-proving secrets/identifiers. Returns { text, redacted: [kinds] }. */
|
|
838
|
+
function redactHubText(value) {
|
|
839
|
+
if (!hubBoundaryEnabled()) return { text: value, redacted: [] };
|
|
840
|
+
let text = String(value);
|
|
841
|
+
const redacted = [];
|
|
842
|
+
for (const [kind, pattern] of HUB_REDACTION_PATTERNS) {
|
|
843
|
+
pattern.lastIndex = 0;
|
|
844
|
+
if (pattern.test(text)) {
|
|
845
|
+
pattern.lastIndex = 0;
|
|
846
|
+
text = text.replace(pattern, "<redacted>");
|
|
847
|
+
redacted.push(kind);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
return { text, redacted };
|
|
851
|
+
}
|
|
852
|
+
|
|
809
853
|
function hubTextFindingKinds(value) {
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
if (
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
function assertHubWorkOrderBoundary(order) {
|
|
835
|
-
const issues = [];
|
|
836
|
-
const fields = [["taskBrief", order.taskBrief]];
|
|
854
|
+
// 인코딩 우회로 숨긴 자격증명도 같은 기준으로 본다(판정만, 치환은 원문 기준).
|
|
855
|
+
return redactHubText(decodedHubText(value)).redacted;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
/**
|
|
859
|
+
* Redact self-proving secrets from the Hub-bound WorkOrder in place.
|
|
860
|
+
*
|
|
861
|
+
* This replaced a reject-and-ask-the-model-to-rewrite gate. Refusing was the
|
|
862
|
+
* wrong shape twice over: the model cannot remove a phrase that IS the task, and
|
|
863
|
+
* a real pasted credential should never depend on a model choosing to drop it.
|
|
864
|
+
* Redaction is deterministic, keeps the run alive, and the caller reports what
|
|
865
|
+
* was masked so nothing is hidden from the user.
|
|
866
|
+
*/
|
|
867
|
+
function redactHubWorkOrder(order) {
|
|
868
|
+
const redactions = [];
|
|
869
|
+
const applyField = (fieldPath, value, assign) => {
|
|
870
|
+
if (typeof value !== "string" || !value) return;
|
|
871
|
+
const { text, redacted } = redactHubText(value);
|
|
872
|
+
if (!redacted.length) return;
|
|
873
|
+
assign(text);
|
|
874
|
+
for (const kind of redacted) redactions.push({ path: fieldPath, kind });
|
|
875
|
+
};
|
|
876
|
+
applyField("taskBrief", order.taskBrief, (next) => { order.taskBrief = next; });
|
|
837
877
|
order.roleSlots.forEach((slot, index) => {
|
|
838
|
-
|
|
878
|
+
applyField(`roleSlots[${index}].title`, slot.title, (next) => { slot.title = next; });
|
|
879
|
+
applyField(`roleSlots[${index}].task`, slot.task, (next) => { slot.task = next; });
|
|
839
880
|
});
|
|
840
|
-
|
|
841
|
-
for (const kind of hubTextFindingKinds(value)) {
|
|
842
|
-
issues.push({ path: fieldPath, code: kind.startsWith("secret_") ? `hub_${kind}` : `hub_private_${kind}` });
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
if (issues.length) {
|
|
846
|
-
fail(
|
|
847
|
-
"work_order_hub_boundary_rejected",
|
|
848
|
-
"Hub-bound WorkOrder free text failed the deterministic privacy boundary",
|
|
849
|
-
{ issues },
|
|
850
|
-
);
|
|
851
|
-
}
|
|
881
|
+
return redactions;
|
|
852
882
|
}
|
|
853
883
|
|
|
854
884
|
function validateWorkOrder(value) {
|
|
@@ -923,7 +953,12 @@ function validateWorkOrder(value) {
|
|
|
923
953
|
if (!Number.isInteger(policy.minimumCandidatesPerSlot) || policy.minimumCandidatesPerSlot < 2 || policy.minimumCandidatesPerSlot > 30) fail("work_order_invalid", "selectionPolicy.minimumCandidatesPerSlot is invalid");
|
|
924
954
|
if (!Number.isInteger(policy.maximumCandidatesPerSlot) || policy.maximumCandidatesPerSlot < 2 || policy.maximumCandidatesPerSlot > 100) fail("work_order_invalid", "selectionPolicy.maximumCandidatesPerSlot is invalid");
|
|
925
955
|
if (policy.minimumCandidatesPerSlot > policy.maximumCandidatesPerSlot) fail("work_order_invalid", "candidate window minimum exceeds maximum");
|
|
926
|
-
|
|
956
|
+
// 자기증명 자격증명만 결정적으로 마스킹한다(거절 아님) — 마스킹 내역은
|
|
957
|
+
// 비파괴 필드로 달아 호출자가 화면에 정직히 표시한다.
|
|
958
|
+
const hubRedactions = redactHubWorkOrder(order);
|
|
959
|
+
if (hubRedactions.length) {
|
|
960
|
+
Object.defineProperty(order, "__hubRedactions", { value: hubRedactions, enumerable: false });
|
|
961
|
+
}
|
|
927
962
|
return order;
|
|
928
963
|
}
|
|
929
964
|
|
|
@@ -2712,6 +2747,12 @@ function create(deps = {}) {
|
|
|
2712
2747
|
// 실황 내레이션: 사용자는 "누가 소집됐고 지금 뭘 하는지"를 보면서 신뢰를
|
|
2713
2748
|
// 형성한다(2026-07-27 오너 요구). 결과에 영향 없는 표시 전용 — silent 존중.
|
|
2714
2749
|
if (!ctx.silent) {
|
|
2750
|
+
const redactions = workOrder.__hubRedactions || [];
|
|
2751
|
+
if (redactions.length) {
|
|
2752
|
+
ui.info(ui.lang === "ko"
|
|
2753
|
+
? `허브 전송 전 ${redactions.length}건 마스킹: ${[...new Set(redactions.map((row) => row.kind))].join(", ")}`
|
|
2754
|
+
: `redacted before leaving this machine (${redactions.length}): ${[...new Set(redactions.map((row) => row.kind))].join(", ")}`);
|
|
2755
|
+
}
|
|
2715
2756
|
const nameByRelease = new Map();
|
|
2716
2757
|
for (const slotRow of candidateSet.slots) {
|
|
2717
2758
|
for (const cand of slotRow.candidates) nameByRelease.set(cand.agentReleaseId, cand.name || cand.agentReleaseId);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentlas",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Agentlas agent terminal — chat with your installed AI agents and teams from the terminal, Claude Code style. Standalone: no desktop app required.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"agentlas": "bin/agentlas.cjs"
|